3.1. New modules programming rules

3.1.1. Syntax comments

  • use three whitespaces as indentation

  • Keep textwidth <= 120 columns (Alternatively, you can donate PDT a few ​Macbooks Pro with Retina Display)

  • put comments one space after #else and #endif using following rules::

    #ifdef VAR
    #endif /* VAR */
    
    #ifndef VAR
    #endif /* !VAR */
    
    #ifdef VAR
    #else /* !VAR */
    #endif /* !VAR */
    
    #ifndef VAR
    #else /* VAR */
    #endif /* VAR */
    

3.1.2. Programming style

  • before allocating array check if it’s not allocated, or use diagnostics:my_allocate.

  • before deallocating array check if it is allocated, or use diagnostics:my_deallocate.

  • all sanity checks should die gracefully.

  • add keyword BEWARE to code comments if you’re introducing a regression.

  • if creating a new module make it private by default, public only things that are necessary.

  • when using module, take only what you need.

  • use protected attribute for public entities that are not meant to be modified outside the module they belong to.

  • make sure that routines meant to be run only once know about it (logical, save :: first_run is good way).

  • variable names should be self-explanatory, i.e. x45gulxzz is not the best idea...

  • write and print should normally be called only from thread number 0 and only via dataio_pub:die,warn,prininfo.
    • this allows e.g. to change output stream in one line rather than few hundreds.
  • avoid code replication - when the same thing is coded more than once, try to merge all implementations into one entity.

  • avoid using magic numbers in size/length declarations, MPI communication, etc., especially when the same numbers are used across multiple files.
    • if you need a small number, use one of small* parameters from mpisetup​ or tiny(1.0) intrinsic.
  • use intent(in|out|inout) attribute in declaration of all variables passed as arguments.

  • avoid using precompiler directives for reasons other than optimization.
    • If your long procedure uses N directives it usually means you need to write N smaller ones.
    • use if or case statement if it doesn’t introduce severe performance dropout.
    • procedure pointers can do magic too ;)
  • Use the init_prob subroutine from initproblem.F90 only for setting up the initial conditions. It is not called on restart, so everything else should be set up in the problem_pointers and read_problem_par subroutines.
    • procedure pointers should be set up in the problem_pointers subroutine. When some procedure pointers depend on parameters from problem.par set them up in read_problem_par subroutine.
  • Do not allocate private arrays for miscellaneous fields outside grid_container. This will not be compatible with AMR. Use grid_container%q(:) array and associated methods instead (since r4765).

  • When you bind a procedure to a type, consider embedding it in a separate module if it does not cause circular dependencies.

  • Put only one module in a single file.

3.1.3. Committing changes

  • Use bin/svnci.py script for commits! . It will call bin/ga.py to warn you about some risky or non-elegant code pieces.
    • You can also call bin/ga.py with list of files you want to check before committing.
  • Call make in the before committing, to check that all setups in obj* directories build correctly.
    • Call make resetup if you added or deleted some source files or you think that new dependencies can break compilation with old Makefiles.

More advices can be found in SVN guide.

QA Checks ​———-

  • implicit saves:
    ! THIS IS THE WRONG WAY

    real :: ke = 0.0

    ! THIS IS THE RIGHT WAY

    real :: ke ke = 0.0

A local variable that is initialized when declared has an implicit save attribute. ke is initialized only the first time the function is called. On subsequent calls the old value of ke is retained. This is a real suprise to C programmers. To avoid confusion and bugs it is mandatory to add the save attribute to such locally initialized variables explicitly.