Christian Maioli M.

🛠 Web development and 🖥 computer programming

Category: Uncategorized

Strategies for dealing with poor code in limited time

You’ve been given the task of implementing a new feature on an old codebase, but the code looks awful. How can you understand it as quickly as possible? Here are several shortcuts to help learn the important parts of new code without getting lost in the irrelevant details.

As programmers, we often have to join new projects, and the quality of the code can be all over the place. Even with an organized team, keeping code quality consistent throughout a medium-size-to-large project is a challenge.

That’s why understanding poor code quickly can be a valuable skill to have. It can help you become very productive in a short time and reduce the stress that usually comes with being the new guy and having to play catch-up. Being in a conversation with a co-worker and not knowing what that person is talking about half the time is a terrible feeling.

On the other hand, this is a prime opportunity for showing your client or boss your skills and that you can get up to speed quickly and impress them. Most developers take weeks to months to become really productive with a codebase they did not build themselves.

Ask for help

Other people have already learned how the code works, so why not ask them about it? You might think it makes you look like the newbie, but showing curiosity can have a strong impact on your image as an employee. If your boss’s expectation was for you to get productive fast without asking questions, that’s a misjudgment on her part.

Everyone takes time to get up to speed. Ask questions, and you’ll impress the people who have the right attitude for teamwork.

In many cases, the original developers will have made strange or unexpected decisions, and that’s where talking about the code will be much more valuable than reading it. This is even more the case if the documentation is lacking. Remember, existing developers have valuable project knowledge that is not written anywhere.

Make a list of new concepts

There might be business concepts that are new to you or that are overly complex. It’s important to get clarification about them before trying to work on code that handles those concepts, to avoid misunderstandings that might take a while to figure out.

It might even be the case that those ideas are mislabeled or represented in an unexpected way in a database. Avoid getting stressed over wrapping your brain around that, and just go to the source and ask your co-workers about it.

In the same vein, there might be modules, classes, or entities that don’t have appropriate names. Make a note of them. Poorly named elements can lead to great confusion, so document them early, as well as anything else that will negatively affect your ability to think about how the code works.

Make it easy to reproduce bugs

By adding code versioning and a virtual machine such as Docker or Vagrant, you will greatly reduce the time it takes to reproduce a bug and to test your work on a new feature.

Any kind of misunderstanding about how the code works could lead you down a path of building the wrong thing, either because what you’re building might already be there and you haven’t seen it, or because things just don’t work the way you thought.

At this point, you want to have Git version control in your project. That way you’ll be able to go back to a stable release, or even just work on separate branches that can be discarded if needed.

It’s even possible to gain greater reproducibility with Git, since you can use the stash to add testing or debugging code while you dig into a hard-to-track problem.

To learn about your project’s architecture, take on configuration and documentation tasks early on.

The same can be said about VMs and reproducibility. They’ve become ubiquitous for any modern development team, but you will certainly run into projects that are not using them or even ready to run inside one. Sometimes your first step as a new team member is to document the steps you took to get an environment working, and eventually turn those steps into a VM setup script.

Build a diagram of components

A mind map of business concepts, an entity-relational diagram (ERD) of the database tables, and a simple diagram of code components can greatly help to reduce the pain of understanding new code. Don’t remember how something works? Keep that ERD open.

In fact, any graphical tool that helps you digest information quickly and have a ten-thousand-foot view of a project will be valuable. Other examples of tools that could help you there are dependency graphs, logs, and a map of the project’s technology stack.

One of the greatest time consumers in development is the integration point between systems. Having a global view of the project landscape will help you identify which integration points are interesting to examine. Those are the spots that have the most work put into them, and the most bugs.

On the other hand, technology moves fast, and there could be opportunities to replace large parts of the codebase with more modern and properly integrated solutions. An old framework might have developed a new and official way to solve a problem, or an entirely new library could have been developed with better compatibility in mind.

Use visualization and modeling tools to view the big picture.

Prepare for automated testing

Before you start breaking things, it’s always prudent to add relevant unit tests. The problem with testing and poor code is that poor code is usually tightly coupled and hard (if not impossible) to test. You can’t isolate components that are intertwined and indivisible.

In those cases, take a step back and test from farther away. Usually that means doing integration testing, for which there are many tools available. Web apps can be tested against HTTP requests, so it is at least possible to verify that the system will respond in the same way to the same inputs.

Integration testing has much worse performance than unit tests, though. Whenever you can, implement unit tests so you can have faster feedback on code changes. If that’s not possible, then opt for functional or even integration testing.

This step should shed some light on the parts of the code that need work. If there’s a large amount of tightly coupled code, that’s a good target for refactoring after integration tests are done, and then for unit testing later.

Identify unusual or inadequate coding strategies

It’s time to start doing some refactoring, but where do you start?

Usually, the best place is where things look weird, or where development best practices have not been followed. For web development, that could mean fat controllers with tightly coupled model code.

Keep in mind that the same strategies might be in use elsewhere. So if, for example, you identify that fat controllers are present, then it’s likely that previous developers did not attempt to have thin controllers. You can expect to see the same problem in other controllers as well, since that reflected the style or shortcomings of the development process before now.

At first, work on a small task

Fixing a small bug on a feature that is conceptually simple will be very enlightening and will help you feel productive from the start.

This is a similar idea to what Andy Hunt and Dave Thomas call “tracer bullets” in The Pragmatic Programmer. The underlying logic is the same: Work on something end-to-end to prove to yourself that it’s possible, and then progressively improve on that initial work.

The “tracer bullet” approach. Credit: The Pragmatic Programmer

A good example of the kind of simple improvement you can make is to take small refactoring steps with simple code. Identify a common programming practice that is not being followed, and apply it.

One of my favorites for this is un-nesting conditional blocks. So instead of having several if-if-if blocks, one inside the other, negate the first one and return, and do the same for all validation-type checks you can find. This can be as simple as inverting an “if” check and moving some code around, so the risk is almost non-existent and the flat code will be easier to read.

Be sure to work first on refactoring features that are easy to test, because even though the changes are low-risk, it’s always a good idea to be able to validate your code before sending it to production.

Get on familiar ground before tackling critical code

Always learn how the project is set up first, and only then dive into the architectural side. The most critical pieces of business and integration code are the hardest to understand and modify. Avoid getting into trouble early on.

As a general rule, avoid business issues that would require you to know more than you currently do about the project, or about the business side. That usually means to stay away from transactional, payments or math-heavy code until it’s starting to become familiar ground.

Once you are productive, comfortable with the coding style for the project and have no trouble fixing simple issues, it’s time to work on the harder stuff—but not before.

Even if there is some urgency for fixing payment issues, for example, that kind of task can be incredibly risky. A mistake there could cost the project dearly, and that will be on you as well. Absolutely refuse to work on high-risk tasks early on, if at all possible.

How to deal with an unfamiliar tech stack

For the last point, a common problem I’ve run into is that every new project usually includes some technology that I’m not familiar with.

When that happens, I have a couple of strategies that help me get up to speed fast. The obvious path for learning is reading documentation and getting familiar with the new technology. But while you will learn a lot about structure, that path will not help you learn the corner cases, which typically come with experience. (“Corner case” is an engineering term that refers to a situation that occurs outside of normal operating parameters.)

One way to speed up this process of gaining experience is to take question-based resources such as Stack Overflow and Quora and just read through them like a book. Find the most popular questions about the library you’re learning and see what kind of problems other people typically run into. You’re not going to necessarily run into them yourself, but just knowing what’s possible is like shining a bright light onto your mind map of those new ideas.

Leverage Stack Overflow questions to gain experience fast. Credit: Stack Overflow

For a more targeted approach, find information about library features being used in your new project specifically. Look into those that are new to you and learn them ahead of time, before you have to touch that code at all.

Use Task Familiarity to get More Accurate Estimates

From the first time I did an estimate, it seemed evident to me that previous experience with a task is tremendously helpful in estimating it accurately. For instance, if you’ve installed a particular library a bunch of times, you should at this point have a good idea about how long that usually takes you, right?

What the current methodologies do

Each person on a team has different levels of familiarity with each task, so it doesn’t matter if one person estimates all tasks, or if you take an average from estimates done by several people. In both cases, there is loss of valuable information.

Strangely, this familiarity factor has been largely ignored by teams I’ve worked in. My current one does “planning poker” for example, which tries to have a few people agree on an estimate. Problem is, what if only one of them has actually implemented such a task before? The weight is on that guy to get everyone else to agree with him. Averaging estimates can lead to completely useless results.

Waterfall had a similar, if not worse, problem. Usually in that kind of environment, the team leader would do the estimating, or he would defer that to another team member. Again, programmer experience was ignored. Never have a single person do all the estimating.

Peer pressure sweeps the problem under the carpet

Another relevant factor that is usually ignored during estimates is: who is actually going to implement it? Certainly, the estimate cannot be accurate if you label a task as low complexity and then assign it to someone with zero previous experience with such a task. This problem cannot be avoided by any system that separates estimating from implementation.

Some teams try to mitigate this by having the implementer re-estimate it, but then you are wasting time estimating it twice and, in my experience, if the estimates differ greatly, there is going to be peer pressure on the implementer to try to get him to agree with the previous estimate.

The benefits of focusing on task familiarity

An interesting thing about paying attention to task familiarity, is that it seems like people have no interest at all in gaming this metric. They know that they either do or do not know something, so there is naturally going to be less guesswork.

If you love numbers, you can run a survey in your team to measure how familiar each person is with each task. By starting from more accurate data, your conclusions should be more valuable.

Start by building a table with team members and tasks, and have each cell be a number from 0 to 10 measuring how familiar each member is with that task. Having done that, you can use that table to help you make decisions and to calculate valuable things.

 

Measuring how familiar each person is with a task can give you valuable insight on your team

  • To maximize implementation speed, simply assign each task to the person most familiar with it.
  • To maximize the potential of your team, have people learn more by assigning somewhat unfamiliar tasks to each one. Keep them in a challenging but not pharaonic effort level.
  • Get people up to speed quickly by teaming up new members with those that are already familiar with the tasks you’ve assigned to the new guys.
  • Build a general technology familiarity table for your team, measuring each tech (React, Angular, etc.) against how familiar each member is with it, which will let you know how much of an impact you’ll have by introducing the new tech into your stack.

Powered by WordPress & Theme by Anders Norén