What are some programming habits you follow to keep bugs away?

Like putting literals and constants on the left hand side in conditional statements to avoid assigning.

Learn Something New Everyday,
Connect With The Best Developers!

Sign Up Now!

& 500k+ others use Hashnode actively.

Mark's photo
  • Write automated tests for features, either before or after the 'real' code.
  • When fixing any bug, write a test so it can never come back unnoticed.
  • (Note that all this testing is not just for having tests; it also encouraged modular design and dependency injection).
  • Use linters for style and to find potential bugs.
  • Use feature branches that check the tests and linters mentioned above before merging. Reviews are also great, if you're not alone.
  • Use types for more than primitives. E.g. make types for Name, Id<Person>, EuroAmt (currency), UnsafeString...
  • Write readable code first. Only optimize after measuring performance and writing unit tests.
  • Use asserts (or require or whatever) to document assumptions.
  • When in doubt, fail. Fast and loud.
  • Avoid mutable state where possible:
    • Mark object fields final. Maybe all the fields.
    • Prefer communicating through arguments and return values rather than changing state.
    • Especially prevent sharing mutable state across threads.
    • Use immutable collections (many languages don't have them built-in, unfortunately, but find a way).
  • Use a language that has non-nullable types.
  • Keep units of code as independent as possible, exposing only what they need to know about eachother. Design patterns help here.
  • Be willing to throw things out. Schedule time for refactoring. Sometimes it was designed wrong, other times it grew out of control, but it has to be fixed.
  • Don't overdo inheritance (no big class trees).
Show +2 replies
Mark's photo

formerly known as M

  • Make steps that belong together an single operation, to make sure you can't forget one of them. This includes communicating all input/output through arguments/return and not getters/setters (as was mentioned).
Mark's photo

Currently focussed on [every programming languages and all of the projects]

Kind of standard advice, but it should be included:

  • DRY: Don't repeat yourself (except caching, which can be wiped). Things will eventually go out of sync and cause bugs.
  • KIS: Keep it simple. Code should be idiomatic and simple in 99+% of the cases. Only complicate it if necessary (likely for performance, so after measuring), after you have unit tests and documentation for it.
Vishwa Bhat's photo

CleanCode + IDE Linters + Design patterns = keeps most of the bugs away

cedric simon's photo

I like your example, never used that.

In my case, I have the habit of always setting a default value to ALL my variables.

Also, I try to never change them unless it makes sense in a specific context.

[redacted]'s photo

I am fortunate to not be clever enough to create any but the simplest bugs, unless the bug is in the language or system itself, in which case, should Google fail, it's despair.

Ben Wa Mso's photo

I tend to think irrespective of all tools we might be using to write code, attention to detail really matters. Also, do unit testing.

Ruurd Pels's photo

No errors. No warnings. Clean desk. Clean repository.

Richard Uie's photo

Expanding on Vikrant Singh Chauhan's idea with examples and explanations...

One of the simplest habits is to leverage the compiler/interpreter when coding logical equality phrases. For example, the conditional statement

if (bob == 6) {-do something-};

The likeliest thing to go wrong is to neglect the second "=", giving the assignment statement

if (bob = 6) {-do something-};

This says, assign 6 to bob. Presuming that the assignment is legal, the logical value of the assignment is TRUE, and the conditional operation will be performed EVERY time the statement is encountered, regardless of the original value of bob before it became 6. That error produces two bugs for the price of one: 1) bob probably has the wrong value; 2) the test always resolves to TRUE. To avoid this trap, never write equality tests in the order {variable_name == literal} - always test logical equality in the order {literal == variable_name}. This rule rewrites our example case as:

if (6 == bob) {-do something-};

This removes any possibility of a sneaky error like

if (6 = bob) {-do something-};

No compiler/interpreter is going to allow you accidentally to reassign the value of a literal.

Mark's photo

Currently focussed on [every programming languages and all of the projects]

No compiler/interpreter is going to allow you accidentally to reassign the value of a literal.

Except Python 2 where you can do True = False. (I suppose it's not really a literal, but mostly works like one). Fixed since Python 3 though.

Ro's photo

Write really simple code.

Simple does not mean short. Sometimes it's better to be more verbose and lay out the steps and variable names clearly. In other words it's not simple to write simple code.

I also write incrementally, making sure everything works solidly as I go.

I also try to keep dependencies inside the code at a minimum, and expose various low-level functionality, so that I can test and experiment and get a better understanding of what is happening. A debugger can help too. What I don't like about a debugger is that it's like being on tracks and only being able to go forward.

At one of my jobs I got into arguments with another developer because he followed the philosophy that "everything must be private" (except what needs to be used). I follow a different philosophy to make my software easier to diagnose.

Formal testing is good. However quick and dirty assertions can also do the job in some cases.

Despite that, sometimes I run into hard-to-track bugs. They are often caused by complexity in whatever the code is interfacing with, for example, the DOM.

My philosophy is about keeping complexity under control - keeping things simple and transparent.

That includes minimizing side effects. Side effect are evil. We have many of these in CSS (eg: oveflow:hidden affects floats and sticky positioning).

I guess that pushes me towards functional programming.

klvenky's photo

Following a coding style guide, could be your own or of the organization you work for.

Also, making sure it's easy to read the code, there by tracking down bugs would be easy. I say tracking down because, I know bugs are meant to be there in any code, at least when coded by a developer like me, it's better to have a simpler code that performs little slower than a very robust code that is very fast and difficult to understand in case of any bugs. Because in my experience, bad variable names cause more issues than a bad code does.

Also trying to make the code align with latest standards and design patterns.

Gijo Varghese's photo

Less Code, Less Bugs. Try it, it works!

Ygor Lazaro's photo

I think the best habit you should have is to fully understand the specification and current processes your software are being used. I know some develops who only think about how programmatically solve a problem, instead to understand why does this problem exist and what it implies to the domain.

Technically speaking, unit and integration tests, small changes, pure functions and small functions, and if possible, test your change with some other programmer. I am a bit fan of pair programming in some terms.

Gergely Polonkai's photo

Let me quote my answer to the latest #FridayDevThoughts with the same topic:

Planning. A lot of it. And integration tests. Also a lot. Also, Iʼm not always utilise TDD, but when I find a bug, the first thing I do is writing a failing test before.

Grammar errors aside, my main weapon is testing. You can format your code whatever you like, if the logic doesn’t meet the requirements, nothing will help.

I try to make sure my code is modular enough that it can be unit tested at all.

Then I set up contracts between the caller and the callee: if function f() gets this and this kinds of arguments, it should return this type of value. In case of Python (my main language as of now) I document these in the function/method docstrings, which is continuously checked by PyLint, although it can’t enforce it.

As a next step, I write unit tests that check if the function fulfils that contract. This is the hard part, especially when I change the contract.

And finally, I write integration tests. I really don’t like it, because when I get to this point, I usually know by heart what the application should do, and writing them as code (be it Behave or pure Python) is a true burden. Still, not as bad as filling a 100-row questionnaire about our security solutions within the company…

Going back one step, I also plan a lot. According to my WakaTime profile I usually code only 13-15 hours a week. The rest is meetings (maybe 5 hours, 10 tops) and planning. This doesn’t mean my plans are always perfect, though; last week we argued on something with a colleague of mine for two days when I realised what he’s telling is a much better idea.

And when I implement a bad idea anyway, or simply add some bugs, my first step is to write a failing test on the integration side. During the time I debug the application to find the root of the problem, I also add new unit tests to functions that don’t have one yet (no, reaching 100% coverage is not that easy), so my code gets better and better every time.

Want to read more?

Browse featured discussions

© 2020 · Hashnode