7

I believe this is a little related with this question: How to be a zero-bug programmer?. But I believe this is more regarding clumsiness in programming because I know it is impossible to become a zero bug programmer.

Often, I write a very buggy code caused by my own clumsiness. For example in writing a currency rate conversion function, a trivial function. I can code the function that running well given the correct value. But sometime I just forgot to validate some unexpected-but-reasonable values.

So are there any ways for clumsy programmer like me to write and test the code more thoroughly?

Additional I see a lot of people suggesting to write unit test or using TDD approach. But writing unit-test not always goes well because of clumsiness isn't it? I don't because I once tried to write unit-test, but still often bumps with bugs.

2
  • 1
    Have a look at how to deal with "edge cases", this question is the top result for common edge cases programmers.stackexchange.com/questions/83814/…
    – StuperUser
    Commented Jan 3, 2012 at 16:15
  • It is not impossible, but it requires an up-front effort.
    – user1249
    Commented Jan 4, 2012 at 0:45

10 Answers 10

19

Try a different development approach: Test-Driven Development (TDD).

a software development process that relies on the repetition of a very short development cycle: first the developer writes an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test, and finally refactors the new code to acceptable standards...

Test-driven development is related to the test-first programming concepts of extreme programming, begun in 1999, but more recently has created more general interest in its own right.

Programmers also apply the concept to improving and debugging legacy code developed with older techniques...

Write test cases and use unit tests to prevent bugs from materializing.

8
  • 10
    Writing tests first forces you to think about the edge cases before you start implementing any logic, which will often result in code that is much less prone to bugs through 'clumsiness'. Commented Jan 3, 2012 at 16:09
  • 9
    @Mike Partridge: I sometimes envy the programmers who can't see the corner cases. When I'm writing code I spot most of the time where it could go wrong, and it makes it very hard for me to actually continue without getting lost in handling corner cases.
    – orlp
    Commented Jan 3, 2012 at 18:55
  • 1
    @nightcracker: I know what you mean. At times I've had to 'mark my backtrail' so to speak, so that I didn't forget all the details that occurred to me while brainstorming corner cases. Using a text editor with liberal use of indenting works well enough for this. Commented Jan 3, 2012 at 19:27
  • 1
    I leave lots of comments as I'm going too - // check null here, etc. so that I don't forget and also don't interrupt what I'm typing right then.
    – Michael K
    Commented Jan 3, 2012 at 19:30
  • 1
    if you are in clumsy mode, your test will be also "clumsy", so in this case you will have not only broken code, but also broken test.
    – Dainius
    Commented Jan 4, 2012 at 10:07
8

Spend a little time as a tester - it will teach you to get into the "edge-case" mentality

Then put all your edge cases into unit tests for a TDD approach..

In addition to TDD, I think reading about Defensive Programming would be helpful too. Learning to assert your preconditions and postconditions in every method is a good practice, and be strict! :)

1
  • 1
    +1 Spot on. I have read that 80% of your development effort is covering edge and fringe cases. The same should apply to testers as well.
    – maple_shaft
    Commented Jan 3, 2012 at 15:11
5

Think more about the requirements. Few people are really clumsy or unable to think of bugs; most just don't take the time to think about the appropriate aspects. If you take a little time and just think about the requirements of a function, you'll discover most of the necessary aspects.

Test-driven development is a hip and well-documented approach to get you focused on thinking about low-level requirements first.

5

In addition to TDD, I'd advise going back to the code a day or two after you've written it, and take a look at what your tests check for and what the function itself checks for. When coding, you may have a very specific idea of how the function will be called, which may become vaguer if you take some time. In addition to that, this is a great chance to take a look at how well-documented the function is, and add comments if you don't understand why it works.

You could also try coding with a Design by Contract framework, which can help catch some errors -- in your example, perhaps checking that the sign of the result is equal to the sign of the input, and that the result (if it's a float) is not NaN or infinity.

1
  • Peer review is also good for this.
    – user1249
    Commented Jan 4, 2012 at 0:48
4

Learn other languages, preferably of different paradigms.

For instance if you've been using an imperative/OOP language like Java/C#/C++, try a functional language like Scala/Haskell/F#. (I suggest Haskell for your purpose since it's the most faithful to the paradigm.)

The advantage of learning a new language or new paradigm is that you will start seeing conceptual patterns that these other languages would have defined rigorously, and then when you go back to working in your language of choice, you can apply these patterns to your code and thus avoid bugs that used to be common otherwise.

TDD isn't the same thing as unit testing. In fact, many TDD practitioners will insist that TDD should not be used to catch bugs or regressions -- and that if you're writing TDD tests to catch bugs, you're losing the design benefits of TDD.

If you want to do testing to improve code quality, do unit testing and integration tests -- not TDD. Sure with TDD you'll develop a habit of spotting bugs up front, but that aspect of TDD really isn't much different from normal unit tests.

3
  • 1
    If I could I would add like +100 for this answer and especially for link tdd link.
    – Dainius
    Commented Jan 4, 2012 at 10:12
  • I thought TDD is very related to unit testing. Guest need to take a look at your link
    – junxiong
    Commented Jan 5, 2012 at 7:56
  • 1
    @junxiong It is, but TDD tests have a very specific purpose -- namely, as a strict (and I would argue overkill) way of encouraging good design. Unit tests written for the purpose of good TDD won't be as effective in catching bugs as unit tests written specifically to catch bugs, and TDD tests written as if they were to catch bugs can often lead to the same kinds of bad designs that TDD strives to avoid in the first place. In other words, whichever you do, you should be mindful of the purpose of your tests. Commented Jan 5, 2012 at 9:09
3

Testing is important but the testing itself will require a patience and an attention to details and so it does not go well with clumsiness, to start with. I'd rather suggest using languages with stricter type systems (in addition to the testing, not instead of it).

Stupid coding mistakes are very rare even for languages like Haskell - if it compiles at all, it is likely to run as expected. And such an errors are nearly impossible for the languages with stricter type systems, like Agda or Coq.

2
  • I think it is rather hard to implement this idea because it is language-specific, isn't it? But I do agree with your point about testing itself. Writing testing doesn't go well with clumsiness
    – junxiong
    Commented Jan 3, 2012 at 23:13
  • @junxiong, which idea? Choosing a language? Well, I agree that there are many problem domains which won't benefit much (or at all) from being coded in a strictly typed language. But many other problems are even expressed cleaner with such languages.
    – SK-logic
    Commented Jan 4, 2012 at 8:53
3

Stop and read your code very carefully before you even compile it. Think like the compiler- find any syntax errors (once you've gotten good, the compiler should never find a syntax error). Also work through the code in your head very thoroughly, play computer. View it as if you only have one chance to hit compile, and everything better work (in other words, be very careful about what you do, don't mash the build command and then fix what pops out). This is a simple technique that is surprisingly effective.

1
  • Amen! Use your brain. It's more powerful than the world's fastest supercomputer. If you don't understand exactly what you wrote does, it should not be in there. Commented Jan 4, 2012 at 0:31
1

There are very few people who can write the error-free code just from scratch. I personally feel always uneasy if rather long chunk of my code compiles and runs immediately and my intuition fails only seldom.

As long as you accept the these that you can not write the error-free code directly it is easier to find a trade-off between the efforts you invest into your code and the (potential) frequency of bugs in it.

One very good approach to minimizing the bugs in your code is the already mentioned Test-driven development. I use it often when I speak in public by adapting it in the following way.

Every independent piece of code is written and re-written in several iterations consisting of the following:

  1. Design the main idea of what you code should do.
  2. Write down the schematic representation of your algorithm.
  3. Re-write the schematic representation so that it is syntactically correct.
  4. Write some most important unit tests for your code (smoke test, for example).
  5. Implement the algorithm to satisfy your existing test(s).
  6. Refactor your code to improve it.
  7. Repeat from 1-4 (depending upon the code maturity) for every iteration and every new feature.

Here are some examples.

(1) I start with something like

I want to calculate pi, so I will simulate a target being a circle with diameter d and use random numbers to cast arrows to it. I will then use the proportion of circle area to the total target size to calculate pi.

(2) Then I move to

Request size of the target.
Request number of arrows. Cast arrows randomly to the target.
Calculate the arrows that met the target.
Calculate the pi value.

(3) I re-write it in C# (just an example):

var Radius = RequestFromUser<double>("Radius of Target");
var NumberOfShots = RequestFromUser<long>("Number of shots");
long GoodShots = 0; long TotalShots = NumberOfShots;
while (--NumberofShots > 0)
{
  if (ShootTarget(Radius)) GoodShots++;
}
Output(CalculatePi(GoodShots, TotalShots));

(4) I create all method stubs in IDE (automatically). This allows me to write my unit tests in semi-automated way (get some templates automatically created for them)
(5) I design the smoke test:

var computed = ComputerPi(Radius: 10.0, NumberOfShots: 10000);
Assert.IsTrue(computed > 3.0 && computed < 3.5);

(6) I quickly (meaning without polishing the code) implement all stubs to start testing.
(7) As long as my tests shows green I re-factor my implementation trying to make it "nicer".
(8) I repeat from (5) if my implementation is not complete, or from an earlier step if I want to extend it.

This helps me to keep my code rather clean, and the slowly emerging unit tests serve as regression tests if I do too dramatic changes to my code.

1
  • Unfortunately this method converges rather slowly. Perhaps this would be a good occasion to show a scaling problem, and how you fix it by replacing the algorithm?
    – user1249
    Commented Jan 4, 2012 at 0:50
0

Here's some good practises:

  1. Step outside of the system
  2. Enumerate all alternatives
  3. Find the border of the system
  4. Consider whole state space
  5. Accuracy in deciding types - only allow exactly what was required
  6. Consider path from input to output -- don't break the path.
0

The real answer is very simple and boring and doesn't involve tons of lists and instructions.

Code every day for 10 years and you will be great at it. That's it. You will end up figuring everything out along the way and refactor and refine yourself. Its the nature of the human brain.

Bruce Lee ended up figuring out that there is no one style of fighting, you have to adapt to your opponent.

In programming, there is no one style, you adapt to your problem. But you have to have done it for a long time to be a master at it.

That's it.

2
  • Did you mean by code for a long time will cure the clumsiness itself eventually? I am not quite convinced. I think I should find the way actively to overcome this...
    – junxiong
    Commented Jan 5, 2012 at 0:54
  • The point is dedication will resolve this. You will refine your technique over time. Commented Jan 5, 2012 at 1:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.