So you've written a website with pages of search-engine-friendly text and you know how many unique visitors you are getting from your weblogs. But how much of your perfectly crafted prose is actually being read?
You could argue that with the weblogs of a site containing many small pages linked together you would have that information. But whenever I navigate to an article that has half a dozen pages, I know it certainly frustrates me.
So if you are writing long pages (ie, with more text that can fit on the screen in one viewing) you are going to need a way of working out whether people have actually bothered to read all that you are writing.
And once again, Ajax comes to the rescue.
Using javascript it's possible to figure out the position of the scroll bar. And using Ajax it's easy to tell the server about this position.
You could even write some server-side code to distinguish between a quick browse down the page and a full-on page read.
Monday, February 04, 2008
Tuesday, January 29, 2008
Engineering Problems on Large Software Projects
Size matters.
If you are on a large software project, you are going to face a bunch of problems that you just won't see on small developments.
And by large projects I mean multi-programmer developments based on a library of modules written over a number of years.
I said, "based on a library of modules". If you don't make your code strictly modular, you've already failed. That isn't always easy, especially when it's the day before a deadline and it's far quicker to bodge in a fix.
Given that you have a bunch of modules, with different versions of them used over several products or versions of the product; maintaining a formal build process is essential. Builds should be numbered or labelled and it should be trivial to reproduce any previous build from you version control system (you do have a version control system, don't you?)
Some formal structure to record who has what build is also needed.
Code re-use generates code as structural patterns are needed to ensure your new module can communicate with the existing functionality.
And with all this extra interfacing code, finding the guts of a particular function or class becomes a real issue. Although Google intranet search tools can certainly help here.
If you've only ever hacked together 2-day projects in your bedroom, these issues will be alien to you. Anyone who's worked in a real software house should be familiar with most if not all of them.
If you are on a large software project, you are going to face a bunch of problems that you just won't see on small developments.
And by large projects I mean multi-programmer developments based on a library of modules written over a number of years.
I said, "based on a library of modules". If you don't make your code strictly modular, you've already failed. That isn't always easy, especially when it's the day before a deadline and it's far quicker to bodge in a fix.
Given that you have a bunch of modules, with different versions of them used over several products or versions of the product; maintaining a formal build process is essential. Builds should be numbered or labelled and it should be trivial to reproduce any previous build from you version control system (you do have a version control system, don't you?)
Some formal structure to record who has what build is also needed.
Code re-use generates code as structural patterns are needed to ensure your new module can communicate with the existing functionality.
And with all this extra interfacing code, finding the guts of a particular function or class becomes a real issue. Although Google intranet search tools can certainly help here.
If you've only ever hacked together 2-day projects in your bedroom, these issues will be alien to you. Anyone who's worked in a real software house should be familiar with most if not all of them.
Friday, January 25, 2008
The new SI units
"Have you heard? The smiths won the lottery last week and the house they're looking to buy has a garden the size of 2 football pitches!"
But a football pitch can vary in size from 4,050m2 to 10,800m2.
And new mp3 players are often quoted as being able to store a certain number of songs, for example the 1Gb iPod shuffle can store 240.
But is this 240 average 3½ minute radio edits?
Or 240 20-minute drum and bass epics?
Or does it even matter?
Using a commonly known "thing" means far more to the average user than an exact figure. Approximating information is a good thing.
If you're only presenting information with 1 or maybe 2 significant figures, do you really care how accurate the unit is?
But a football pitch can vary in size from 4,050m2 to 10,800m2.
And new mp3 players are often quoted as being able to store a certain number of songs, for example the 1Gb iPod shuffle can store 240.
But is this 240 average 3½ minute radio edits?
Or 240 20-minute drum and bass epics?
Or does it even matter?
Using a commonly known "thing" means far more to the average user than an exact figure. Approximating information is a good thing.
If you're only presenting information with 1 or maybe 2 significant figures, do you really care how accurate the unit is?
Monday, January 07, 2008
Company Culture
We hear about company culture all the time, but until you've worked in a few companies it's hard to understand how two software development houses can feel so different to work at.
A team based company will normally separate groups off into their own little rooms or sections of the open plan office. There is likely to be a hierarchical structure to each team with a one or two Chiefs and then many Indians.
Games companies typically tend to have this structure as game building is very much a large team activity, especially with the modern consoles and large budgets that accompany them.
Consulting companies tend to treat staff as experts in their field. The company understands that if their staff can't solve a problem, they'll find someone who can.
In this type of company you'll be given freedom to work and you'll be treated like an adult. But you may be left to just get on with it.
This type of company would tend to employ someone with experience rather than a fresh graduate and the office is likely to be fully open plan.
Small, agile, and with time to market a priority, start-ups can be fun, energetic and hectic. If you're lucky enough to have an office you'll be sharing it with just a handful of others, some of whom may have experience but all of whom will be passionate about what they do.
Expect to roll out core software on Monday.
Explain it to your client on Tuesday.
Fix a bug in it on Wednesday.
Deploy a fixed version late Thursday evening.
And spend Friday afternoon in the pub with the directors celebrating a great week.
Don't be surprised if you get asked to help build the company website or install a company-wide version control system as the company will be too small to have a dedicated expert in that field.
The Team
A team based company will normally separate groups off into their own little rooms or sections of the open plan office. There is likely to be a hierarchical structure to each team with a one or two Chiefs and then many Indians.
Games companies typically tend to have this structure as game building is very much a large team activity, especially with the modern consoles and large budgets that accompany them.
The Consultancy
Consulting companies tend to treat staff as experts in their field. The company understands that if their staff can't solve a problem, they'll find someone who can.
In this type of company you'll be given freedom to work and you'll be treated like an adult. But you may be left to just get on with it.
This type of company would tend to employ someone with experience rather than a fresh graduate and the office is likely to be fully open plan.
The Start-up
Small, agile, and with time to market a priority, start-ups can be fun, energetic and hectic. If you're lucky enough to have an office you'll be sharing it with just a handful of others, some of whom may have experience but all of whom will be passionate about what they do.
Expect to roll out core software on Monday.
Explain it to your client on Tuesday.
Fix a bug in it on Wednesday.
Deploy a fixed version late Thursday evening.
And spend Friday afternoon in the pub with the directors celebrating a great week.
Don't be surprised if you get asked to help build the company website or install a company-wide version control system as the company will be too small to have a dedicated expert in that field.
Tuesday, December 18, 2007
Working code is worth its weight in gold.
Write your tests.
Write your code.
Update your diagrams.
Done.
See the mention of copious reams of documentation here?
No?
That's because there really is no need to write them.
Why?
Because there is no point in spending hours of programmer time trying to write a document that will never be read.
Class diagrams are useful to quickly illustrate the architecture to newcomers but that's all the additional documentation you should need to explain how things work.
If your code is written well - i.e. well laid out classes with suitably short, well-named functions that do just one thing - the tests and the working code will be sufficient to easily explain what it does and how it does it.
It should be like looking up the word "dictionary" in a dictionary. It's functional - in giving the definition - and it also explains what it is.
Write your code.
Update your diagrams.
Done.
See the mention of copious reams of documentation here?
No?
That's because there really is no need to write them.
Why?
Because there is no point in spending hours of programmer time trying to write a document that will never be read.
Class diagrams are useful to quickly illustrate the architecture to newcomers but that's all the additional documentation you should need to explain how things work.
If your code is written well - i.e. well laid out classes with suitably short, well-named functions that do just one thing - the tests and the working code will be sufficient to easily explain what it does and how it does it.
It should be like looking up the word "dictionary" in a dictionary. It's functional - in giving the definition - and it also explains what it is.
Monday, December 10, 2007
How to find a career you love
I while back I posted that to be successful you should be in a job you love.
And it's good to see that Steven Covey (of The 7 Habits of Highly Effective People fame) agrees with me!
He says:
So far, his blog is definitely worth a read.
And it's good to see that Steven Covey (of The 7 Habits of Highly Effective People fame) agrees with me!
He says:
Discovering your dream job involves asking yourself these basic questions over time :
(1) What do you really love to do?
(2) What do you do well?
(3) What should you do so that you tap into your true voice?
(4) What does the world need?
So far, his blog is definitely worth a read.
Tuesday, December 04, 2007
C++ Programming Tips
Inserting into a sorted std::vector
MyVec.push_back(MyInt)
if (MyVec.size() > 1)
{
std::inplace_merge(
MyVec.begin(),
MyVec.begin() + (MyVec.size() - 2),
MyVec.End());
}
This won't be the fastest way of performing an insert, but it will probably be the smallest amount of code. For the fastest way, look at finding with a binary search, then inserting.
Disclaimer: If you are going to be inserting many things into the middle of a vector, you probably shouldn't be using a vector. A list would be more suited.
Constructor variable initialisation order
This is a subtle C++ism that I forgot about and caught me out today.
When initialising items in a constructor's initialisation list, the order of initialisation is dependant on the order the variables appear in the class definition, not the initialisation list.
An example will help:
class MyClass
{
MyClass();
int a;
int b;
int c;
};
MyClass::MyClass() :
b(0), c(0), a(0)
{
}
In the above example, a, will be initialised first followed by b, then c.
Friday, November 30, 2007
The Superstar Premium
An interesting article appeared today on the BBC website about the Superstar Premium. It basically says that the superstars are getting richer because of nu-media allowing them to be a global phenomenon. Instantly.
The Superstar Premium idea also applies to products such as music and video games.
For example video games fall into two categories, triple-A games and the rest. The AAA games go on to sell millions worldwide, whilst the rest - although costing virtually the same amount to make - can sell just a few thousand.
Even though the Premiership footballers and the triple-A games aren't thousands of times better than the rest, they earn tens or hundreds of times more. Why? Because they are better.
And when you are excellent, being just a couple of percent better - to make you the best - makes you so much more valuable. After all, who wants to finish second? And if you can sell your brand of "the best" product to a worldwide audience you're going to make a considerable amount of money.
The Superstar Premium idea also applies to products such as music and video games.
For example video games fall into two categories, triple-A games and the rest. The AAA games go on to sell millions worldwide, whilst the rest - although costing virtually the same amount to make - can sell just a few thousand.
Even though the Premiership footballers and the triple-A games aren't thousands of times better than the rest, they earn tens or hundreds of times more. Why? Because they are better.
And when you are excellent, being just a couple of percent better - to make you the best - makes you so much more valuable. After all, who wants to finish second? And if you can sell your brand of "the best" product to a worldwide audience you're going to make a considerable amount of money.
Wednesday, November 28, 2007
AJAX Breaks my Back Button.
My last post about whether programmers should be allowed to wear headphones sparked a fair bit of debate, both in the comments and on the dzone website.
So, today I'm going to continue the controversy by talking about the problems with AJAX.
Now, don't get me wrong, I'm a massive fan of the potential of AJAX.
But it breaks the browsers back button.
This might not sound like such a big deal, but there are millions of web users who like to surf through links randomly, aimlessly wandering their way through cyberspace. To them, the back button is their piece of string in the dark cave of the internet.
Without it, the internet doesn't work as a link stack any more.
AJAX is essential for the future of the web, but developers should make it their priority to try to fix the problems that is produces.
So, today I'm going to continue the controversy by talking about the problems with AJAX.
Now, don't get me wrong, I'm a massive fan of the potential of AJAX.
But it breaks the browsers back button.
This might not sound like such a big deal, but there are millions of web users who like to surf through links randomly, aimlessly wandering their way through cyberspace. To them, the back button is their piece of string in the dark cave of the internet.
Without it, the internet doesn't work as a link stack any more.
AJAX is essential for the future of the web, but developers should make it their priority to try to fix the problems that is produces.
Monday, November 19, 2007
Should Coders be Allowed to Wear Headphones at Work?
I mean I love my music - I played as a resident DJ for a year spending all my student loan on records! - but when I'm at work, I'm there to work, not to be entertained.
I also understand that when you've got one of Nokia's latest remixes blasting out every five minutes, having some background music - some familiarity - can be a good way to get into the coding zone. Especially if you've got your bug hunting hat on:

But what about senior coders?
Doesn't it say in their job description that they are meant to help and mentor their more junior peers?
But how can they recognise the signs of a coder-in-need if they are taking in their latest track from iTunes?
And then for managers and those in a leadership position, those who should be aware of the rest of the team at all times, how can they lead fully when they don't hear any or all that happens?
It seems like there must be a happy medium somewhere between headphones on productivity and headphones off knowledge sharing. But it also seems that the further up the tree you go, the less you should be wearing them.
I also understand that when you've got one of Nokia's latest remixes blasting out every five minutes, having some background music - some familiarity - can be a good way to get into the coding zone. Especially if you've got your bug hunting hat on:
But what about senior coders?
Doesn't it say in their job description that they are meant to help and mentor their more junior peers?
But how can they recognise the signs of a coder-in-need if they are taking in their latest track from iTunes?
And then for managers and those in a leadership position, those who should be aware of the rest of the team at all times, how can they lead fully when they don't hear any or all that happens?
It seems like there must be a happy medium somewhere between headphones on productivity and headphones off knowledge sharing. But it also seems that the further up the tree you go, the less you should be wearing them.
Friday, November 16, 2007
Can't Delete?
So you want to delete a file? Or move it? Or simple rename it?
But every time you try, XP throws up an access denied message:

The thing to do is to download Unlocker. In fact, it's so good you should download it now. Go on.
But every time you try, XP throws up an access denied message:

The thing to do is to download Unlocker. In fact, it's so good you should download it now. Go on.
Wednesday, November 14, 2007
There are only 6 numbers you need to know when testing ...
... and they are -1, 0, 1, some, n - 1 and n.
Generally, if you algorithm works with these 6 variations on the input (-1 being the error case) it'll work with anything you want to throw at it.
If you've tight on time, I'd recommend testing at least 0, n and 1 (in that order) as they are normally the problem children.
And there is always an argument about how your algorithm should handle error cases. Does it cope with them softly by returning false or throwing an error, or violently by asserting?
Both are correct.
At least one should be present.
But if you are writing code with unit tests, testing inputs [-1, 0, 1, some, n - 1 and n], those tests will show how your functionality is suppose to be used and the ambiguity is reduced.
Generally, if you algorithm works with these 6 variations on the input (-1 being the error case) it'll work with anything you want to throw at it.
If you've tight on time, I'd recommend testing at least 0, n and 1 (in that order) as they are normally the problem children.
And there is always an argument about how your algorithm should handle error cases. Does it cope with them softly by returning false or throwing an error, or violently by asserting?
Both are correct.
At least one should be present.
But if you are writing code with unit tests, testing inputs [-1, 0, 1, some, n - 1 and n], those tests will show how your functionality is suppose to be used and the ambiguity is reduced.
Wednesday, October 03, 2007
Ask the Audience - Take the Majority.
So you're on Who Wants to be a Millionaire and you have question that you don't know. It may not be all that difficult but you just don't know the answer to it.
So you ask the audience.
When you ask the audience you take the response with the highest percentage. You go for the majority as you don't know these people and you can't trust their knowledge.
The same would be true if you were to use internet users to provide answers, as the ReCAPTCHA people are doing. They are using captchas as a way as identifying words in old books and manuscripts.
To ensure that the word they get back is actually the correct word, they ask the same question to two people, if they both agree, fine. If not, they ask more people until they have a majority vote.
One of the main problems with the Javascript stream processing algorithm is the worry of hackers doping the results. But, by asking the same question of multiple browsers - by asking the audience - you can be very sure of getting the correct answer.
So you ask the audience.
When you ask the audience you take the response with the highest percentage. You go for the majority as you don't know these people and you can't trust their knowledge.
The same would be true if you were to use internet users to provide answers, as the ReCAPTCHA people are doing. They are using captchas as a way as identifying words in old books and manuscripts.
To ensure that the word they get back is actually the correct word, they ask the same question to two people, if they both agree, fine. If not, they ask more people until they have a majority vote.
One of the main problems with the Javascript stream processing algorithm is the worry of hackers doping the results. But, by asking the same question of multiple browsers - by asking the audience - you can be very sure of getting the correct answer.
Friday, July 20, 2007
Tiscali the inept broadband provider
So after waiting for a month for Tiscali to connect broadband, we had a month of decent usage before the connection went down. 4 weeks and countless calls to support later they still hadn't managed to connect us so we cancelled the contract, quoting a contract clause. This clause basically says if we haven't received service for 28 days we should consider the contract finished.
We won't be paying this inept company for the month of non-service and we won't be paying them anything for cancelling the contract either.
We won't be paying this inept company for the month of non-service and we won't be paying them anything for cancelling the contract either.
Sunday, June 10, 2007
Stream Computing with AJAX (Part 3)
I mentioned in Part 2 of Stream Processing with Javascript that I would talk about its morality.
However, there is something more pressing. I've been trying to work out exactly how to stop users reverse-engineering the Javascript Stream Processing system and injecting spurious results. However, it seems that it is a common issue with Calculate@Home projects. From the Folding@Home FAQ:
The main problem with AJAX stream computing is that users haven't deliberately gone and downloaded the software to help the cause, they are having the calculations forced upon them. Because of this, it is likely that there would be more hostility towards any such project and hackers would be more likely to try to cause malicious results.
As the code is executed on the client, and by the very nature of Javascript being interpreted, it is very easy to reverse-engineer any such code. And because it's so easy to reverse-engineer, it's very difficult to do any sort of encryption/confirmation.
Of course not everyone out there is going to try to hack the system, so one solution might be to calculate each batch of data on several different machines and then, given a discrepancy, recalculate that batch.
Still to discuss, optimisation - in particular bandwidth optimisation through local data storage - and morality.
Edit: Since writing this I've realised that you just need to take the majority vote from a number of results
However, there is something more pressing. I've been trying to work out exactly how to stop users reverse-engineering the Javascript Stream Processing system and injecting spurious results. However, it seems that it is a common issue with Calculate@Home projects. From the Folding@Home FAQ:
Unlike many computer projects, the paramount concern is not functionality, but the scientific integrity, and posting the source code in a way that would allow people to reverse engineer the code to produce bogus scientific results would make the whole project pointless.
The main problem with AJAX stream computing is that users haven't deliberately gone and downloaded the software to help the cause, they are having the calculations forced upon them. Because of this, it is likely that there would be more hostility towards any such project and hackers would be more likely to try to cause malicious results.
As the code is executed on the client, and by the very nature of Javascript being interpreted, it is very easy to reverse-engineer any such code. And because it's so easy to reverse-engineer, it's very difficult to do any sort of encryption/confirmation.
Of course not everyone out there is going to try to hack the system, so one solution might be to calculate each batch of data on several different machines and then, given a discrepancy, recalculate that batch.
Still to discuss, optimisation - in particular bandwidth optimisation through local data storage - and morality.
Edit: Since writing this I've realised that you just need to take the majority vote from a number of results
Thursday, May 31, 2007
To Initial or not...
I've worked in places where it's the norm to leave your initials as you comment:
And now I'm working in a place where it's not:
What I've discovered is that by getting your coders to add initials to their code, they will feel like they own that section of code. This could be considered an advantage - in that they'll take pride in their code sections. But also a disadvantage as, if they see a bug and someone else has their initials near it, they may be reluctant to fix that bug.
I think the initials method works better in teams where the code roles are very much well defined and the code is unlikely to be reused over and over (like for example, game code that tends to be very specific and rarely reused).
The non-initial method means that it's not my code, it's our code. This method is more suited to programmers in teams where the code is heavily reused such as in many engineering disciplines and in library code.
// IH: my comment about some functionAnd now I'm working in a place where it's not:
// But who wrote this in the code?What I've discovered is that by getting your coders to add initials to their code, they will feel like they own that section of code. This could be considered an advantage - in that they'll take pride in their code sections. But also a disadvantage as, if they see a bug and someone else has their initials near it, they may be reluctant to fix that bug.
I think the initials method works better in teams where the code roles are very much well defined and the code is unlikely to be reused over and over (like for example, game code that tends to be very specific and rarely reused).
The non-initial method means that it's not my code, it's our code. This method is more suited to programmers in teams where the code is heavily reused such as in many engineering disciplines and in library code.
Tuesday, May 29, 2007
Stream Computing with AJAX (Part 2)
In my introduction to AJAX Stream Processing I introduced the idea of using web browsers as stream processing machines. The idea being that stream computing kernels and data packets were downloaded to browsers using AJAX technology and calculated on the client.
So how does it actually work?
Let's take the problem of testing the primality of a number,
So a datastream is created containing
A kernel to test the primality of
Calculating prime numbers through AJAX stream processing shows this theory in practice.
There are some issues with security (along with the normal AJAX problems):
The third problem is commonly solved through the use of a proxy.
Next time, I'll discuss the morality issues of (sneakily) using a client's computer to do complex calculations for your means.
So how does it actually work?
Let's take the problem of testing the primality of a number,
n. A naive implementation is to check if n is divisible by any of the primes up to n / 2.So a datastream is created containing
n along with all of the primes up to n / 2 and this is sent via AJAX calls to the client. Next a kernel program is downloaded to the client and executed via Javascript's window.eval() function.A kernel to test the primality of
n would look something like this:
isPrime = true;
for (var i = 0; i < primeArray.length; i++)
{
if (n % primeArray[i] == 0)
{
isPrime = false;
break;
}
}
{n, isPrime} is then returned to the server - another AJAX transaction - and saved into a database. Finally, the Javascript application asks for a new number to test and the cycle continues.Calculating prime numbers through AJAX stream processing shows this theory in practice.
There are some issues with security (along with the normal AJAX problems):
- What if the user decides that they are going to change the data returned using some kind of Javascript debugger?
- How do you guarantee server-side that you are getting a valid processed result?
- How do you get around the
XMLHttpRequestsecurity, stopping it from contacting one site from another (as would be needed with a host site showing the "calculation banner")?
The third problem is commonly solved through the use of a proxy.
Next time, I'll discuss the morality issues of (sneakily) using a client's computer to do complex calculations for your means.
Wednesday, May 23, 2007
Stream Computing with AJAX (Part 1)
How many of you out there sit and read internet pages?
And how much work is your processor doing when you sit there reading the BBC News pages?
Virtually none.
Wouldn't it be great if all the computers around the world that were just sat there when people were reading websites could be used to help the Folding@Home or Seti@Home projects?
Enter Javascript's eval and XMLHttpRequest commands.
These two commands provide us with the technology to retrieve packets of data, process them, and return the result to the server - all without the website's user having to do anything (or indeed, any knowledge that this is even happening).
This stream processing system could be triggered by an ad banner included as a Javascript URL from the server (strictly, the banner doesn't even need to be visible).
"But who would want to slow their website down by including such a banner?"
I hear you cry.
"Those with an eye on their bank balance!"
I swiftly reply.
Many financial institutions are crying out for more processing power and would quite happily pay good money to have their packets of data processed. Webmasters could easily be coerced into including the stream processing AJAX program if they knew they were receiving some money for each packet computed by their readers.
And off course I'm waiting to skim a humble percentage off the top... :)
In the next post I'll discuss some of the finer points of how stream processing actually works, and some of the issues about doing this in Javascript, such as security and recovery.
And how much work is your processor doing when you sit there reading the BBC News pages?
Virtually none.
Wouldn't it be great if all the computers around the world that were just sat there when people were reading websites could be used to help the Folding@Home or Seti@Home projects?
Enter Javascript's eval and XMLHttpRequest commands.
These two commands provide us with the technology to retrieve packets of data, process them, and return the result to the server - all without the website's user having to do anything (or indeed, any knowledge that this is even happening).
This stream processing system could be triggered by an ad banner included as a Javascript URL from the server (strictly, the banner doesn't even need to be visible).
"But who would want to slow their website down by including such a banner?"
I hear you cry.
"Those with an eye on their bank balance!"
I swiftly reply.
Many financial institutions are crying out for more processing power and would quite happily pay good money to have their packets of data processed. Webmasters could easily be coerced into including the stream processing AJAX program if they knew they were receiving some money for each packet computed by their readers.
And off course I'm waiting to skim a humble percentage off the top... :)
In the next post I'll discuss some of the finer points of how stream processing actually works, and some of the issues about doing this in Javascript, such as security and recovery.
Back again
Ok, so I've been a bit quiet over the past few months. But I started a new job and that has been taking up a large chunk of my time.
I've also (finally) got myself a broadband connect in my flat. It's with Tiscali and after initially waiting for over a month and being repeatedly fobbed off by their over apologetic call centre staff, it's now running smoothly.
I've also (finally) got myself a broadband connect in my flat. It's with Tiscali and after initially waiting for over a month and being repeatedly fobbed off by their over apologetic call centre staff, it's now running smoothly.
Monday, December 11, 2006
Job Hunting
As I mentioned previously, I've been hunting for a job for a little while now.
Both of the companies that I sent a CV out to directly did not even have the decency to reply back to me, so I decided to use a recruitment specialist.
If you are looking for a programming career in Cambridgeshire I would recommend ecm selection as they did a wonderful job advising me on my CV and helping me to get the job that I really wanted.
Both of the companies that I sent a CV out to directly did not even have the decency to reply back to me, so I decided to use a recruitment specialist.
If you are looking for a programming career in Cambridgeshire I would recommend ecm selection as they did a wonderful job advising me on my CV and helping me to get the job that I really wanted.
Subscribe to:
Posts (Atom)