Use constants.

Avoid numbers and string literals.

Whenever a number appears in the code, it often represents a business concept. For example, maybe there’s a for loop that iterates 20 times, where 20 represents the number of rows on a page. That “20″ should not be there; what you really meant to code was “number of rows per page,” so make a constant, “NUMBER_OF_ROWS_PER_PAGE,” and use that instead.

In a large codebase, the chances are good that this “20″ concept appears more than once. And then one day, someone decides that you should only show 15 rows per page. So the developer changes the code in one place… and overlooks the “20″ in the other place. Next thing you know, you have an inconsistent codebase. Use constants to avoid this dumb mistake.

The same applies to string literals. When you see these in the code, it’s just like the problem with the “20″. Assign the string to a constant, and use it there.

For views where there is a lot of text, you should consider using an i18n solution. Not only does this keep all your consumer-facing text in one place, but it also makes it easy to support multiple languages.

That all said, some magic numbers are okay to stay as-is. You don’t need to replace “0″ with a constant for the start of the array. You don’t need to replace “100″ with a constant PENNIES_PER_DOLLAR or TOTAL_PERCENT. These are everyday concepts that aren’t going to change and become a problem later.

Don’t put queries in loops.

Poorly designed database queries are the leading cause of performance problems.

From time to time, you will come across an application that appears to operate slower than expected. The cause of the slowness may come from many different sources. The server may be CPU-bound. The network bandwidth may be tapped out. The log server may have slow disk drives. It can be a lot of things. Computers are really fast and powerful, and networks are high-speed, so most of the time, a performance problem comes down to the database.

Poorly written SQL can cause a lot of problems. For example, a simple query to fetch a few rows of data can turn into a table scan if the query does not use an index. In a large table, the difference in performance can be dramatic. Imagine in one case the index is looked up and the database server returns a few rows, while in the other case the database server has to look at every row in a 100-million row table to determine if it matches and it should return them. Table scans are brutal when running on large tables.

As bad as table scans are, cartesian joins can be even worse. In that scenario, for every row in a table scan, the server may have to run another table scan on another table. In those cases, with large tables, the query may take so long that the database may appear to be hung. Obviously, one should try to avoid these problems as much as possible.

One thing to do is to avoid running SQL queries inside a loop. When you do this, you’re basically performing the equivalent of a cartesian join. You need to rewrite the query so that instead of a loop calling a query thousands of times, the query should only run once and return all of the correct data.

Be careful when joining tables via an ORM. You may get N+1 queries if done incorrectly. Using a query builder correctly takes practice, and you should review the generated SQL to ensure that it’s not missing indexes and sending your performance down the drain.

Always keep a sharp eye on queries that work with the database.

Know your platform.

Don’t reinvent its features.

As a developer, you should resist the urge to develop everything yourself. There are often tools in your framework, libraries, or operating system that can do what you want.

Some people have a bad habit of coding these features themselves. The result of these efforts is usually less effective than the available tools. Generally speaking, it’s better to use a common tool than to invent a solution yourself. Developers working on your code down the road will have an easier time if you used the included tools instead of inventing your own monkey wrenches.

Study your platform, and learn its features. Use them.

Create simpler triggers.

Keep your logic outside of the trigger.

Triggers are blocks of code that are run when a particular event happens, such as the insertion of a database record. In some applications, an event might have several triggers. It’s possible that various developers added them to perform different jobs when the event occurred.

Each trigger may perform some complex data manipulation or calculation. Unfortunately, the order that the triggers execute may be uncertain or random, leading to unexpected behavior. To avoid this problem, there should be only one trigger for any particular event.

Combining actions into one trigger makes for a messy trigger, so you should refactor that logic out. This change reduces the trigger to one job: keeping the order of actions to be performed.

For example, if creating a shipment has two triggers, one of which reserves space on a truck and one that updates adds promotional items to the box, then you should combine those two jobs into one trigger. Move the logic for each of the two jobs to separate methods. The trigger will merely call the two jobs. The order of the jobs is essential because adding promotional items to the box may affect how much weight should be reserved on the truck.

Write better acceptance criteria.

This skill goes a long way towards solving problems before they begin.

Learning to write good acceptance criteria can be challenging, but there are ways to make it easier. Start by writing them in simple English. Acceptance criteria should be easy to understand, without any technical jargon. Use the terminology from the business domain.

The benefits of having good acceptance criteria are long-lasting. Not only will it save countless hours of development time and rework, but it can also prevent frustrating bugs that often result from the misunderstanding of vague requirements. As a result, the return on investment in the effort is too high to ignore. So it certainly pays to take the extra time necessary to develop a robust set of requirements.

Well-defined acceptance criteria can also uncover mission requirement details that could delay a project. Too often, developers overlook essential features because the initial requirements were not crystal clear.

All too often, the difference between a high-performing team and a typical one comes down to the requirements given to the developers.

Refactor the spaghetti code.

Learn techniques to replace messy software.

When a section of code looks like a hot mess, it can be challenging to understand what it is doing precisely. The code’s intent gets muddled, and it becomes easy to miss the fine details and particulars. It’s also likely that the test coverage is insufficient for the task.

Your goal should be to unwind this spaghetti code into a collection of several small methods, each with its own unit tests. There are several techniques one should investigate when going about this kind of effort.

The first and most important is to keep methods short, moving logic to other methods. At first, it may seem inane to replace three lines of logic with one method call, but if those three lines belong together, then go ahead and do it. Every little bit helps. Repeating this process throughout the code will unwind a lot of spaghetti.

Sometimes, you can replace nested if statements with boolean logic. This particularly true when all that is inside an if block is another if statement with its own block.

Learn the proper use of map, reduce, and array walk routines. These can simplify iterations. You should also learn to build iterators so that you can hide the complexity of the iteration in its own procedure. This is also handy if you have nested iterations, like when processing data in a multi-dimensional array.

Replace nested if statements with a series of if statements where possible. These can, in turn, be replaced with methods. When there are if statements that are acting as filters, one can sometimes replace them by using request objects.

Read books on refactoring and learn more advanced techniques. Ultimately, you should never have any spaghetti code. When you’re done, the code should be obvious and easy to understand by the casual reader.

Communicating is hard.

What is said is often not what is meant.

Understanding a person’s desires can be more challenging than expected at times. People often choose the wrong words when expressing their ideas, and these can easily be misinterpreted further by those who receive the message. In everyday conversation, this may not be a big deal. But when one is gathering business requirements, errors in understanding can have significant costs.

Thus, one should always ask for clarification. Repeat the message back to the requestor in your own words. Use examples that illustrate the requirement in various scenarios, particularly around edge cases, failure conditions, and unexpected choices. Add pictures, diagrams, and mockups that further describe the system or feature requested. People often understand a problem a lot better when you draw pictures.

Discuss the requested enhancement and its context. Ask what happens before and after, so you can better understand the necessary states of the application.

And most importantly, don’t take simple requests at face value.

Always keep learning.

Take ownership of your own development.

No one is going to care more about your future than yourself. Always remember this. Sure, the company loves its employees and is highly invested in their success. The company wants high-performing employees. Ultimately, though, the company’s goals and your personal goals will not be perfectly in alignment. That’s why you need to take responsibility for your career development.

Maybe that plan will mean staying with the company and getting promoted to positions of higher responsibility. Or maybe it will mean eventually moving on to a different opportunity. The important thing is, one needs to keep learning and growing professionally.

You can teach yourself a lot. Study new languages, techniques, and patterns. Read other people’s code. Open up an open-source project and look under the covers. Watch webcasts about programming. Find blogs on advanced topics. Read highly technical books and manuals.

There are so many things you can do to make yourself better. And if that means sometimes you have to pay for it out of your own pocket, then that’s fine. It’s worth the investment.

Follow the campsite rule.

Leave it better than when you found it.

When you go camping, you bring a lot of stuff with you and make a mess. Being a considerate person, you clean up after yourself before leaving. To be a truly nice person, you then clean up a little more, leaving the campsite in a better condition than when you found it.

The same should apply to developing code.

As you visit a section of code to add your feature or deal with a bug, try to make the code more DRY by refactoring out any duplicated code segments. Also, see if you can apply the SOLID principles to some of the classes under your review.

Alternately, maybe your improvement is as simple as refactoring more substantial methods and classes down to smaller ones, extracting out logic and responsibilities.

Maybe your change will merely add more or better tests. An improvement to a codebase’s tests can be a huge help.

Another way to improve things is to make the code more understandable. This may mean writing more relevant documentation, including explanations of the reasons behind the logic. Or it may mean changing the names of local variables to be more descriptive.

You don’t need to do all of these things. But anything you can do that’s a little extra, above satisfying the current requirement, will reduce the burden on others in the future.

Don’t get clever.

Keep it simple, stupid.

When writing software, one really should endeavor to create programs that are easy to understand. The logic should be obvious and easy to follow. When it comes time to make changes, the developers who come after you will not want the software to meet them with irrational challenges.

Software development must not be an exercise in showing off how intelligent you are. One must resist the temptation to use tricky algorithms and obscure tricks of indirection. While these may provide a solution you consider “elegant,” others will find it difficult to understand your intent.

To make matters worse, the people who have to modify your code will likely be under immense pressure and probably won’t have access to you. If they’re unable to ask you questions, they will have to figure it out on their own. There’s a significant chance that they may misinterpret your intent.

So what are you giving to these future developers? A gift of maintainable code? Or a legacy of difficult challenges that they will need to replace? While the choice seems obvious, one should remember how many times you have encountered complicated code yourself. The urge to replace legacy code is strong because it costs too much to maintain. Only code that clearly reveals its intent will be admired and kept.