How to write Fortran code with few bugs as possible and few debugging time as possible?

Dear all,

This is a question I was thinking for some time, and wanted to seek for your tips in doing Fortran programing.

My style is always just write all the parts of the whole big code, and then compile and run, and find bugs and kill bugs.

I know this is not a good habit. But, it seems in Fortran, if I want to check some subroutine/functions, I need to write some small drive code to test them. But I just too lazy to write drive code every time to check small parts of the code.

Also, I know my this style of writing code, may write code in several days, however may spend weeks to check bugs. It seems just not a very smart way.

I mean, I would rather spend two weeks to write a Fortran code and once it is finished it is already bug free, than spending one day write a code and then spend more than two weeks to check the bugs.

I also notice that, sometimes people say, they use python/Julia/matlab simply because it is easy for them to check small pieces of code on the fly. So overall, it saves them a lot of time. For them, spending less time in doing programing is more important than the speed/performance of the program itself. They say that Fortran may be faster, however, consider the amount of time devoted in writing a code, it is hard to say if it really worth it.

So I wonder, do you have easy ways in Fortran to check each pieces of code on the fly?

Do you have some advice or what is your workflow in write Fortran code?

Thank you very much indeed in advance!

LFortran will fix that. See the recent post LFortran Minimum Viable Product (MVP)

I think there is no miracle: either you spend time writing tests, or you spend time debugging… In Test Driven Development the first solution is considered to be best and faster.

And an advantage of writing tests is that you can more easily optimize or refactor your code: if tests are OK you can feel quite confident that you have not broken your code. Of course you need well designed tests for that.

3 Likes

Note also that it will be easier to write unit tests if you try to write PURE functions, when possible.

And in the TDD, theoretically you should first write the tests, then write the function… In that way you are forced to think about the results you want to obtain, and to think about the interface of your function before writing it. But in practice, it can be difficult to follow that discipline… But really, you would gain more confidence in your code.

1 Like

As @vmagnin says, there is no universal solution. Consider the following program:

program calculate_sum
     write(*,*) 'Hello, world!'
end program calculate_sum

Does this contain a bug? It is a slightly absurd example of course, but without a clear specification of what it is supposed to do, you cannot state it contains a bug. Your expectation, based on the name, may be that it does an arithmetic calculation and therefore it just printing a well-known string is wrong, but there is nothing inherent connecting the name to the function.

A better solution is to be clear about what it is supposed to do - TDD intends to do so via tests. The specification is in the tests you write (ideally before writing the program code itself).

To spend less time writing auxliliary programs that test your code, you might consider using a single skeleton program that simply test the one subroutine/function you are now interested in and reuse the code to invoke another subroutine/function at the next stage. That will save you the trouble of creating new scripts/batch files/project files/makefiles/… and other infrastructure to build the test programs. In the end you are, however, stuck with the situation above: what is a program unit supposed to do?

1 Like

I often write new code in the main program, but when I get it working and want to add new functionality, I refactor the code in the main program into a subroutine or function. That way the next program I write can call those procedures, instead of duplicating code in the previous main program. In general, code that is duplicated more than twice is a sign that procedures with that code should be created.

2 Likes

I heartily agree with that. For a very long time I have encouraged writing every routine you can as a reusable procedure for your libraries and to set up a testing framework for the libraries that is easily added to. Your program should consist of just the parts that are not re-usable. As you build libraries that are fully tested the next code you write will be shorter and require less testing. Libraries are easy to share, programs are usually not. A rule in a course I used to present was “everything should go into a library unless you know why it shouldn’t”.
Another was “test it as you build it”. Another was “trust no data”. By that, it was meant that every routine should check the integrity of the data passed to it in development, and those checks should only be removed if required for performance (the course was called “Paranoid Programming” by the way). Also, although not quite on topic, was “document the code in the code”. People often find writing the documentation helps them to generalize what the routine should and can do among the more obvious reasons for doing that. “Writing maketh a precise man”.

Following those rules and a few others (“Follow the well-trodden path” has to be qualified but generally means stick to using standardized language, and that compilers tend to be the buggiest in the least-used features; and the hardest topic to teach was to track or at least understand accumulated error and to never trust floating point math at face value; a whole other topic) resulted in codes that are still in use today that are decades old that have had no bug reports or only a few (this longevity being aided by being written in a standardized language – Fortran, aka. FORTRAN).

There were others, and the last bit of the class was titled “Rules are meant to be broken”; but long before terms like reuse and unit testing were terms, let alone common terms those rules helped produce very reliable and long-lived codes. They are predicated somewhat on the idea that you are writing something that may be used for a long time, but are useful things to consider even when writing a throw-away or prototype.

Even unpleasant tasks such as “Test, Test, Test” become much easier as you make them a habit and settle onto a testing framework that works for you.

Depending on how long your tests have to be and what type of codes you are writing you may develop testing habits very different than what others need, but the easlier you form the habit the better. The first time your tests find an error in something you “knew” was right you’ll become a fan. Until then it will seem like a chore.

4 Likes

I could (probably) write a book on the topic (and hopefully one of these days I’ll find time to do so). As has already been said, there is no magic bullet. There’s no one tip I could give you and tomorrow you’re writing bug free code. But I can somewhat briefly describe some of the ideas that have helped me do so.

  1. Functional Programming
  2. Test Driven Development
  3. Design Patterns

The ideas of functional programming push one really hard in the direction of smaller procedures, that do fewer things, with clearer interactions, that are easier to put together. This helps to limit the scope of possible bugs, making them easier to find and fix. The resulting code is often easier to understand as well, because it’s clear where all the data comes and goes (the fancy term for this is referential transparency). The biggest rule here is avoid shared, mutable state. The implication then is no module variables, and no setters.

The main idea with TDD is to push you to think a lot harder about what you want a piece of code to do and how you want to use it before you write it. This has the side effect of ending up with clearer specifications, more testable code, and a test suite to go along with it. Find a testing framework you like (I’m obviously partial to vegetables as its author), and find some exercises to practice with (Google for “code kata”). I find it’s a practice I don’t follow as religiously so much any more, but when you’re first learning it’s a good idea to be very methodical and take the tiniest steps possible to really get the hang of it.

With design patterns the idea is to put as many tools as possible in your pocket. Go learn other languages to see what patterns they use often. Read books on design patterns. What you really want is to always have multiple ways you could approach a problem.

Nothing is more dangerous than an idea when it is the only one you have

  • Emile Chartier

The important thing to note is that, just because you are able to think of A way to solve a problem does not mean that is the best way. Always try to evaluate the trade-offs associated with a given design choice versus some other possible design. One design tip I have, which isn’t so much a pattern, “make illegal states unrepresentable” (I can’t seem to find the originator of that quote, but you’ll find lots of material covering it).

Finally, @rouson and I love teaching and consulting for this kind of stuff, so if you’re ever interested in hosting a training session, want a code review, or help on a project, don’t hesitate to reach out.

4 Likes

Online Fortran compilers (there are many available on the web) are fairly easy to use for quick testings and debugging of code snippets. Ideally, a solution like LFortran is desirable. In the meantime, there is a Jupyter binding that enables Fortran coding (including parallel Coarray) in a Jupyter environment (using gfortran underneath) as if you are writing Python: GitHub - sourceryinstitute/jupyter-CAF-kernel: A Coarray Fortran Jupyter notebook kernel

3 Likes

As someone who teaches Fortran and has done for nearly 40 years my first recommendation is a compiler that has good diagnostic capability at both compile time and run time. Nag has been the best for a long time. The Polyhedron site has compiler tables and Jane and I have an article in Fortran Forum comparing our current suite of compilers that we use - Nag, Intel, gfortran and Cray.

9 Likes