Copyright (c) Hyperion Entertainment and contributors.

Difference between revisions of "Coding Guidelines"

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
m (Added link)
 
(9 intermediate revisions by the same user not shown)
Line 1: Line 1:
These coding guidelines are from Heinz Wrobel's 1996 article "Coding Standards" published in the Developer CD V2.1 and supplemented with the AmigaOS API and types recommendations.
+
These coding recommendations originate from Heinz Wrobel's 1996 article "Coding Standards" published in the Developer CD V2.1, supplemented with the AmigaOS API and types recommendations.
   
 
= General =
 
= General =
Line 17: Line 17:
 
No matter what coding style you choose, use it consistently. READABILITY COUNTS.
 
No matter what coding style you choose, use it consistently. READABILITY COUNTS.
   
; Be careful about tabs in your sources
+
; Be careful about tabulators in your sources
   
Tabs can be the most obnoxious things in the world. They can even shut down your work or at least waste valuable resources. Why? Because nobody cares about the details. Due to historical reasons the tab character should represent eight spaces. To be exact it should represent a move to the next column with its number being a multiple of eight. This value of eight spaces is very important as it is '''the only''' value where portability including all alignment of the sources between different OS or editing environments can be guaranteed.
+
Tabulators can be the most obnoxious things in the world. They can even shut down your work or at least waste valuable resources. Why? Because nobody cares about the details. Due to historical reasons the tabulator character should represent eight spaces. To be exact it should represent a move to the next column with its number being a multiple of eight. This value of eight spaces is very important as it is the only value where portability including all alignment of the sources between different OS or editing environments can be guaranteed.
   
It is common practice to use tabs to indent sources. That is most definitely not a bad thing. Unfortunately most brain damaged editing environments allow arbitrary configuration of the tab width for the display and then save these "display tabs" as "disk tabs". This messes up all alignment once the next team member loads the source into his favourite editing environment. This can decrease readability to almost zero, and it is guaranteed that people will get into a "formatting war" instead of doing the job necessary. Obviously this is a totally unacceptable situation. There is only one solution:
+
It is common practice to use tabulators to indent sources. That is most definitely not a bad thing. Unfortunately most brain damaged editing environments allow arbitrary configuration of the tabulator width for the display and then save these "display tabs" as "disk tabs". This messes up all alignment once the next team member loads the source into his favourite editing environment. This can decrease readability to almost zero, and it is guaranteed that people will get into a "formatting war" instead of doing the job necessary. Obviously this is a totally unacceptable situation. There is only one solution:
   
: Configure your editing environment to differentiate between "disk tabs" of eight spaces and "screen tabs". You work with screen tabs and your environment will have to automatically convert between your favourite screen tab setting to the disk tab size of 8 whenever it writes documents to disk. Of course on loading an appropriate conversion to screen tabs has to take place, too. If the environment does not support something like this, trash it or don't use tabs in any source at all. Otherwise you '''will''' have a problem.
+
: Configure your editing environment to differentiate between "disk tabs" of eight spaces and "screen tabs". You work with screen tabulators and your environment will have to automatically convert between your favourite screen tabulator setting to the disk tabulator size of 8 whenever it writes documents to disk. Of course on loading an appropriate conversion to screen tabulators has to take place, too. If the environment does not support something like this, trash it or don't use tabulators in any source at all. Otherwise you '''will''' have a problem.
   
Concerning screen tab expansion, I strongly recommend a setting of four. This allows for easy and efficient conversion to disk tabs and gives a visually effective indent in pretty much any programming language.
+
Concerning screen tabulator expansion, I strongly recommend a setting of four. This allows for easy and efficient conversion to disk tabulators and gives a visually effective indent in pretty much any programming language.
   
Be extremely careful about automatic tab conversions in your editing environment. If there is any chance that other members of your team have to access the sources, automatic tab conversion should be disabled. Otherwise the usefulness of any revision control system in development is severly limited as the sources will change "randomly" between certain tabs and space configurations.
+
Be extremely careful about automatic tabulator conversions in your editing environment. If there is any chance that other members of your team have to access the sources, automatic tabulator conversion should be disabled. Otherwise the usefulness of any revision control system in development is severly limited as the sources will change "randomly" between certain tabulators and space configurations.
   
 
; Mark the closing braces with comments
 
; Mark the closing braces with comments
   
Once you have seen >10 "END" or "}" statements in a row and spent >10 minutes to figure out where they belong, you know what I mean. Never just write "}" and nothing more, write "} /* if */" or "} /* function_foo */". This is important. Modern editing environments can be configured to do this automatically for you.
+
Once you have seen more than ten "END" or "}" statements in a row and spent more than 10 minutes to figure out where they belong, you know what I mean. Never just write "}" and nothing more, write "} /* if */" or "} /* function_foo */". This is important. Modern editing environments can be configured to do this automatically for you.
   
 
; Align matching braces
 
; Align matching braces
   
Having matching braces (as opposed to the K&R style) aligned in one column makes finding the matching brace a lot easier. The brain gets 87% of its outside information via the eyes. Simple improvements in readability like this will save time.
+
Having matching braces aligned in one column makes finding the matching brace a lot easier. The brain gets 87% of its outside information via the eyes. Simple improvements in readability like this will save time.
   
 
Example of a coding style concentrating on readability:
 
Example of a coding style concentrating on readability:
Line 86: Line 86:
 
; Do not fake other languages' behaviour
 
; Do not fake other languages' behaviour
   
Redefining e.g. "{" and "/" as "BEGIN" end "END" with the preprocessor is a misuse of the language. To the experienced it is as misleading as to beginners, and non standard constructs reduce readability because "nobody" knows about them. Using the language as it was defined allows your team to stay with standard references and manuals. If someone does not <ins>want</ins> to use a language as is and <ins>insists</ins> on aliasing its constructs to his favourite thing, they'd be better in some other place. This person will probably misuse any language. Using a screwdriver as a hammer does not lead to results.
+
Redefining e.g. "{" and "/" as "BEGIN" end "END" with the preprocessor is a misuse of the language. To the experienced it is as misleading as to beginners, and non standard constructs reduce readability because "nobody" knows about them. Using the language as it was defined allows your team to stay with standard references and manuals. If someone does not want to use a language as is and insists on aliasing its constructs to his favourite thing, they'd be better in some other place. This person will probably misuse any language. Using a screwdriver as a hammer does not lead to results.
   
 
; Limit function size
 
; Limit function size
Line 94: Line 94:
 
; Limit file size
 
; Limit file size
   
A C source file should never be longer than a few hundred lines. 1 kB lines is the absolute upper limit. Modularity helps a lot with C, too. Consider the linking process to be a feature.
+
A C source file should never be longer than a few hundred lines. Thousand lines is the absolute upper limit. Modularity helps a lot with C, too. Consider the linking process to be a feature.
   
 
; Use header files
 
; Use header files
Line 112: Line 112:
 
; Use a standard header and footer for each source file
 
; Use a standard header and footer for each source file
   
If you are using a Revision control system, the header should contain an identification. This header can be quite simple:
+
If you are using a revision control system, the header should contain an identification. This header can be quite simple:
   
 
<syntaxhighlight lang="c">
 
<syntaxhighlight lang="c">
Line 131: Line 131:
 
/* End of Text */
 
/* End of Text */
 
</syntaxhighlight>
 
</syntaxhighlight>
 
= AmigaOS-defined APIs and Types =
 
 
* Prefer using [[Fundamental_Types|AmigaOS types]] such as '''int32''' and '''STRPTR''' over '''int32_t''' and '''*char'''.
 
* Prefer '''AllocVecTags()''' and '''FreeVec()''' over '''malloc()''' and '''free()'''.
 
   
 
= Programming Language =
 
= Programming Language =
Line 141: Line 136:
 
; Use the ANSI C standard
 
; Use the ANSI C standard
   
Use the ANSI C standard for declarations. Avoid K&R wherever possible. The compiler is your friend and will do better error checking with ANSI prototypes.
+
Use the ANSI C standard for declarations. Avoid K&R (Kernighan & Ritchie) declarations wherever possible. The compiler is your friend and will do better error checking with ANSI prototypes.
   
 
; Use the strictest error checking mode
 
; Use the strictest error checking mode
Line 153: Line 148:
 
; All team members should have access to the language definition
 
; All team members should have access to the language definition
   
Have the language definition in reach for all team memebers. I also recommend having a book like "P.J.Plaugher: The Standard C Library" around as reference to everyone on the team. If in doubt, look it up! It is not a shameful thing to do!
+
Have the language definition in reach for all team members. I also recommend having a book like "[[Coding_Guidelines#Sources_of_Additional_Information|P.J. Plaugher: The Standard C Library]]" around as reference to everyone on the team. If in doubt, look it up! It is not a shameful thing to do!
   
 
; Stay with standards wherever possible
 
; Stay with standards wherever possible
   
If you write a standard C application use the standard ANSI library. If you write something specific to a certain OS, use the tools the OS provides. Avoid arbitrary mixtures of standard C and OS specific C. Avoid use of functions specific to your current C compiler system or specific to the hardware if you want to have protable code. You might need to change both eventually. This is mostly a problem for IBM clone programmers who think that "Borland C++" or "MS C++" and clones is the only thing there is in the world. Watch your team. Check what your compiler does per default with newlines or character sets! "\r\n" vs. "\n" problems can be eventually as painful as a compiler system that mangles ISO characters into 7 bit in strange places. Note that handling binary file data is different than text data with the ANSI C library. Do not rely on it being the same for all compiler systems.
+
If you write a standard C application use the standard ANSI library. If you write something specific to a certain OS, use the tools the OS provides. Avoid arbitrary mixtures of standard C and OS specific C.
  +
  +
When writing for AmigaOS, use the AmigaOS-defined APIs and types:
  +
* Prefer using [[Fundamental_Types|AmigaOS types]] such as '''int32''' and '''STRPTR''' over '''int32_t''' and '''*char'''.
  +
* Prefer '''AllocVecTags()''' and '''FreeVec()''' over '''malloc()''' and '''free()'''.
  +
  +
Avoid use of functions specific to your current C compiler system or specific to the hardware if you want to have protable code. You might need to change both eventually. This is mostly a problem for IBM clone programmers who think that "Borland C++" or "MS C++" and clones is the only thing there is in the world. Watch your team.
  +
  +
Check what your compiler does per default with newlines or character sets! "\r\n" vs. "\n" problems can be eventually as painful as a compiler system that mangles ISO characters into 7 bit in strange places. Note that handling binary file data is different than text data with the ANSI C library. Do not rely on it being the same for all compiler systems.
   
 
; Isolate non-portable sections
 
; Isolate non-portable sections
   
Isolate the non-portable code sections.
+
If your code contains non-portable sections, isolate them.
   
 
; Use const
 
; Use const
   
Use "const" and "volatile". Especially "const" helps the compiler and the poor soul who writes and debugs the code a lot. Wise use of "const" enhances the compiler error checking capabilities usually a lot. Some compilers may generate poor code with these keywords unfortunately. In that case an easy solution is to define them to nothing when generating production code.
+
Use '''const''' and '''volatile'''. Especially '''const''' helps the compiler and the poor soul who writes and debugs the code a lot. Wise use of '''const''' enhances the compiler error checking capabilities usually a lot. Some compilers may generate poor code with these keywords unfortunately. In that case an easy solution is to define them to nothing when generating production code.
   
 
; Limit scope
 
; Limit scope
   
Define anything that does not need to be externally visible as "static". Limiting scope of unneeded stuff helps a) the linker, b) modularity, c) a strong black box approach for modules.
+
Define anything that does not need to be externally visible as '''static'''. Limiting scope of unneeded stuff helps '''a)''' the linker, '''b)''' modularity, '''c)''' a strong black box approach for modules.
   
 
; Use the preprocessor carefully
 
; Use the preprocessor carefully
Line 194: Line 197:
 
; Use Make
 
; Use Make
   
If your compiler system has an automatic build facility like "make", use it. "make" is a very helpful tool. If you have some sort of custom integrated compiler environment, make sure that you can comparatively easy fall back on a standard make like scheme. Be careful about all the different incarnations of "make". Keep the parachute close.
+
If your compiler system has an automatic build facility like '''Make''', use it. '''Make''' is a very helpful tool. If you have some sort of custom integrated compiler environment, make sure that you can comparatively easy fall back on a standard make-like scheme. Be careful about all the different incarnations of '''Make'''. Keep the parachute close.
   
 
; Learn to use pointers
 
; Learn to use pointers
Line 214: Line 217:
 
= Version Control =
 
= Version Control =
   
Use some sort of a revision control system like RCS, CVS, SCCS, etc. Put a file ID comment (like the RCS 'Id' keyword) into each source file. Otherwise identifying a printed revision is pretty much impossible once you get swamped in paper. I use RCS. Do not ever use expanding 'Log' like keywords in your sources. They just duplicate what is in the repository already and they'll make identifying differences even harder. After a while they also clutter the source.
+
Use some sort of a revision control system like RCS, CVS, SCCS, etc. Put a file ID comment (like the "RCS <id>" keyword) into each source file. Otherwise identifying a printed revision is pretty much impossible once you get swamped in paper. I use RCS. Do not ever use expanding 'Log' like keywords in your sources. They just duplicate what is in the repository already and they'll make identifying differences even harder. After a while they also clutter the source.
   
 
Mark stable revisions and flame everyone who makes too many changes to the code before using the revision control system to check it in again. There should not be any work files not being checked in lying around at the end of a working day!
 
Mark stable revisions and flame everyone who makes too many changes to the code before using the revision control system to check it in again. There should not be any work files not being checked in lying around at the end of a working day!
Line 221: Line 224:
   
 
Last but not least: Have fun!
 
Last but not least: Have fun!
  +
  +
= Sources of Additional Information =
  +
  +
Further information on C programming language and coding conventions can be found in:
  +
  +
[[Special:BookSources/978-0138380120|The Standard C Library]], by P.J. Plaugher. Prentice Hall, 1991.

Latest revision as of 20:03, 30 June 2020

These coding recommendations originate from Heinz Wrobel's 1996 article "Coding Standards" published in the Developer CD V2.1, supplemented with the AmigaOS API and types recommendations.

General

The zeroeth law of C programming:

  1. Fully agree on the coding rules BEFORE you start the project and make them available IN WRITING. Revise them as necessary in a group effort and document reasons for changing them.

Code Formatting

First, a few comments about looks and braces:

Be consistent

No matter what coding style you choose, use it consistently. READABILITY COUNTS.

Be careful about tabulators in your sources

Tabulators can be the most obnoxious things in the world. They can even shut down your work or at least waste valuable resources. Why? Because nobody cares about the details. Due to historical reasons the tabulator character should represent eight spaces. To be exact it should represent a move to the next column with its number being a multiple of eight. This value of eight spaces is very important as it is the only value where portability including all alignment of the sources between different OS or editing environments can be guaranteed.

It is common practice to use tabulators to indent sources. That is most definitely not a bad thing. Unfortunately most brain damaged editing environments allow arbitrary configuration of the tabulator width for the display and then save these "display tabs" as "disk tabs". This messes up all alignment once the next team member loads the source into his favourite editing environment. This can decrease readability to almost zero, and it is guaranteed that people will get into a "formatting war" instead of doing the job necessary. Obviously this is a totally unacceptable situation. There is only one solution:

Configure your editing environment to differentiate between "disk tabs" of eight spaces and "screen tabs". You work with screen tabulators and your environment will have to automatically convert between your favourite screen tabulator setting to the disk tabulator size of 8 whenever it writes documents to disk. Of course on loading an appropriate conversion to screen tabulators has to take place, too. If the environment does not support something like this, trash it or don't use tabulators in any source at all. Otherwise you will have a problem.

Concerning screen tabulator expansion, I strongly recommend a setting of four. This allows for easy and efficient conversion to disk tabulators and gives a visually effective indent in pretty much any programming language.

Be extremely careful about automatic tabulator conversions in your editing environment. If there is any chance that other members of your team have to access the sources, automatic tabulator conversion should be disabled. Otherwise the usefulness of any revision control system in development is severly limited as the sources will change "randomly" between certain tabulators and space configurations.

Mark the closing braces with comments

Once you have seen more than ten "END" or "}" statements in a row and spent more than 10 minutes to figure out where they belong, you know what I mean. Never just write "}" and nothing more, write "} /* if */" or "} /* function_foo */". This is important. Modern editing environments can be configured to do this automatically for you.

Align matching braces

Having matching braces aligned in one column makes finding the matching brace a lot easier. The brain gets 87% of its outside information via the eyes. Simple improvements in readability like this will save time.

Example of a coding style concentrating on readability:

void function(int foo, int bar)
{
    switch(foo)
    {
        case 1:
            if(bar == 2)
            {
                /* Do something about it! */
            } /* if */
            break;
        default:
            break;
    } /* switch */
 
} /* function */

Naming a closing brace (or an #endif statement) as described above helps matching them even further, especially when moving through code of a team member.

Always use braces

Always use braces, even for single statements. This helps avoiding errors when code is revised and statements are added.

if(bla)
    foo = test(foo);
else
    foo = test2(foo);

is much more error prone than:

if(bla)
{
    foo = test(foo);
}
else
{
    foo = test2(foo);
} /* if */

With braces, changing code is less dangerous. Without braces it can be very easy to get lost and to overlook a statement dependency when you have to work with code of some other team member. Safety counts and helps to avoid delays and expensive bug fix releases "just because someone overlooked a semicolon" or because of a similar stupid reason.

Do not fake other languages' behaviour

Redefining e.g. "{" and "/" as "BEGIN" end "END" with the preprocessor is a misuse of the language. To the experienced it is as misleading as to beginners, and non standard constructs reduce readability because "nobody" knows about them. Using the language as it was defined allows your team to stay with standard references and manuals. If someone does not want to use a language as is and insists on aliasing its constructs to his favourite thing, they'd be better in some other place. This person will probably misuse any language. Using a screwdriver as a hammer does not lead to results.

Limit function size

Once the text of a function is much more than e.g. two screen sizes, split it up into subfunctions. Huge functions make revisions and maintenance a pain. Typically, performance isn't an issue here and link time is less than compile time.

Limit file size

A C source file should never be longer than a few hundred lines. Thousand lines is the absolute upper limit. Modularity helps a lot with C, too. Consider the linking process to be a feature.

Use header files

Create prototype header files and header files with the externally needed data declarations. Use them as help for making black box modules.

Separate functions

Separate functions with some sort of header or even a simple line:

/*------------------------------------------------------------------------*/

This makes fast reading of source files and understanding their contents a lot easier. Don't use "bold" lines, e.g. lines made up of asterisks '*'. Depending on the display they lose their line-like character and clutter the screen instead of helping to make things readable.

Use a standard header and footer for each source file

If you are using a revision control system, the header should contain an identification. This header can be quite simple:

/*------------------------------------------------------------------------*/
/*                                                                        *
 *  $Id: CodingStandards,v 1.2 1996/07/13 20:51:24 heinz Exp $
 *                                                                        */
/*------------------------------------------------------------------------*/
 
/*------------------------------------------------------------------------*/
[headers here]
 
/*------------------------------------------------------------------------*/
[code here]
 
/*------------------------------------------------------------------------*/
 
/* End of Text */

Programming Language

Use the ANSI C standard

Use the ANSI C standard for declarations. Avoid K&R (Kernighan & Ritchie) declarations wherever possible. The compiler is your friend and will do better error checking with ANSI prototypes.

Use the strictest error checking mode

Always run the compiler system with the strictest error checking mode possible. C compilers have limits to their diagnostic ability, so use what is available. Errors and warnings are not acceptable!

Know the implementation-defined behaviour

Read the "Implementation-defined Behaviour" section of the manual before you start. While there is a standard for C, there are still things that can bite you if you are not aware of them. ANSI C does not automatically mean "great, portable, and future compatible".

All team members should have access to the language definition

Have the language definition in reach for all team members. I also recommend having a book like "P.J. Plaugher: The Standard C Library" around as reference to everyone on the team. If in doubt, look it up! It is not a shameful thing to do!

Stay with standards wherever possible

If you write a standard C application use the standard ANSI library. If you write something specific to a certain OS, use the tools the OS provides. Avoid arbitrary mixtures of standard C and OS specific C.

When writing for AmigaOS, use the AmigaOS-defined APIs and types:

  • Prefer using AmigaOS types such as int32 and STRPTR over int32_t and *char.
  • Prefer AllocVecTags() and FreeVec() over malloc() and free().

Avoid use of functions specific to your current C compiler system or specific to the hardware if you want to have protable code. You might need to change both eventually. This is mostly a problem for IBM clone programmers who think that "Borland C++" or "MS C++" and clones is the only thing there is in the world. Watch your team.

Check what your compiler does per default with newlines or character sets! "\r\n" vs. "\n" problems can be eventually as painful as a compiler system that mangles ISO characters into 7 bit in strange places. Note that handling binary file data is different than text data with the ANSI C library. Do not rely on it being the same for all compiler systems.

Isolate non-portable sections

If your code contains non-portable sections, isolate them.

Use const

Use const and volatile. Especially const helps the compiler and the poor soul who writes and debugs the code a lot. Wise use of const enhances the compiler error checking capabilities usually a lot. Some compilers may generate poor code with these keywords unfortunately. In that case an easy solution is to define them to nothing when generating production code.

Limit scope

Define anything that does not need to be externally visible as static. Limiting scope of unneeded stuff helps a) the linker, b) modularity, c) a strong black box approach for modules.

Use the preprocessor carefully

While it may be useful to give strange constants nice names, use of it should be monitored. Major caveat: C is a language where expressions often have side effects. Side effects hidden in preprocessor macros are not diagnosed by all compiler systems! Do not overdo it here!

Test cases

If a source file has self contained functionality like e.g time conversion stuff, think hard about adding a test case for conditional compile in the first place.

#ifdef TEST
int main(int argc, const char **argv)
{
    /* test the darn thing here! */
} /* main */
#endif /* TEST */
Praise the linker

Remember: the linker is your friend!

Use Make

If your compiler system has an automatic build facility like Make, use it. Make is a very helpful tool. If you have some sort of custom integrated compiler environment, make sure that you can comparatively easy fall back on a standard make-like scheme. Be careful about all the different incarnations of Make. Keep the parachute close.

Learn to use pointers

C is usually most efficient when used with pointers. Passing data structures is not good, most things are done with pointers. If your team does not have a firm understanding of pointers, teach them first. Don't let them fiddle along. They need to know first or you might be in deep trouble.

Always check function results

Check function results for any function that may fail. A common error is not checking for failure on e.g. memory allocations. Assume a negative situation when writing the code. It will hardly ever be a performance hit if you do and increase reliability.

Debugging Code

  • Check for NULL pointers and pointer overruns.
  • C strings have a trailing NULL byte and are therefore one byte longer than the contents!
  • Check for missing "*/" end of comments. You might have lost a line of code.

If a bug is still hiding and can't be found, have the author explain the functionality of the code in question line by line including limit and exception handling to someone on the team who is not directly involved in this part of the project. This will usually solve the problem pretty fast and save additional cost.

Version Control

Use some sort of a revision control system like RCS, CVS, SCCS, etc. Put a file ID comment (like the "RCS <id>" keyword) into each source file. Otherwise identifying a printed revision is pretty much impossible once you get swamped in paper. I use RCS. Do not ever use expanding 'Log' like keywords in your sources. They just duplicate what is in the repository already and they'll make identifying differences even harder. After a while they also clutter the source.

Mark stable revisions and flame everyone who makes too many changes to the code before using the revision control system to check it in again. There should not be any work files not being checked in lying around at the end of a working day!

Afterword

Last but not least: Have fun!

Sources of Additional Information

Further information on C programming language and coding conventions can be found in:

The Standard C Library, by P.J. Plaugher. Prentice Hall, 1991.