Once the software development team has completed the software architecture and the software detailed design, the exacting task of turning the design into code begins. The use and adherence to the project's software coding standards will enhance the resulting code and reduce coding errors (see SWE-061). In a team environment or group collaboration, coding standards ensure uniform coding practices; it reduces oversight errors and the time spent in code reviews. When NASA software development work is outsourced to a supplier, the agreement on a set of coding standards ensures that the contractor's code meets all quality guidelines mandated by NASA-STD-8739.8, Software Assurance, and Software Safety Standard.
The software development team uses accredited tools to develop the software code (see SWE-136). This may include accredited tools that have not been previously used or adapted to the new environment. The key is to evaluate and then accredit the development environment and its associated development tools against another environment/tool system that is accredited. The more typical case regarding new software development is using accredited tools in an accredited development environment that have not been used together previously and thus are not accredited as an integrated system. Many NASA missions and projects have used modeling tools like Simulink and Matlab. Auto-generated code (e.g., code from generators like MatrixX and Real-Time Workshop) has been successfully used in the past. It is an important approach for developing software for current and future NASA projects. The potential for bugs, sometimes difficult to find, and the generated code's certification are two problems the software development team needs to be aware of and plan for as the software coding occurs.
Smaller software work products can be completed with a few stand-alone tools. Larger software work products will benefit from using an integrated development environment (IDE) for producing the code. An IDE (also known as an integrated design environment) is a software application that provides comprehensive facilities to software coders and computer programmers for software development.
An IDE normally includes the following tools:
- Source code editor (a text editor for editing the source code).
- Compiler and/or an interpreter (a program (or set of programs) that transforms source code written in a programming language (the source language) into object code).
- Build automation tools (activities or work aids that script or automate the wide variety of tasks that software developers do in their day-to-day activities).
- Debugger (a program run on the software to surface coding errors or other software work product issues).
An IDE developed for a particular application may have more tools. The Process Asset Library (PAL) at the performing Center is the first to search for an existing IDE to use.
This Handbook, along with the Software Processes Across NASA (SPAN), accessible to NASA users from the SPAN tab in this Handbook, provides an extensive listing of individual tools that have been developed for particular applications (see section 5.1 of this SWE). The tool list contains both NASA and commercially developed products. SPAN has several examples of design environments. PALs from other Centers, easily located by NASA users through the NASA Engineering Network (NEN), are good places to search for individual tools and developed IDEs.
Code generated by using an IDE results in output language from the compiler, usually the native machine language of the system. The work accomplished in this phase of the software life cycle includes the coding of the algorithmic detail developed during the component-level design activities. This results in the coding needed for manipulating data structures, affecting the communications between software components across their interfaces, and implementing the processing algorithms allocated to each software work product component.
The software team performs code unit testing and debugging regularly to help find errors early in the coding cycle to avoid expensive fixes in the systems and integration test case phases of the software life cycle. The use of unit testing is intended to confirm that the software work product performs the capability assigned to it, correctly interfaces with other units and data, and represents a faithful implementation of the unit design. Static analysis tools are used to help uncover various problems in the code (dead code, security vulnerabilities, memory leaks, etc.) Debugging can be done with various tools, but experienced and knowledgeable personnel often are needed when addressing the code that supports complex software architectures and designs. Code walk-through and peers' inspections can help identify and correct issues and reveal opportunities for applying better coding practices.
Compiling may take one pass, or it may take multiple passes. Compiling will generate optimized code. Often the execution of one pass through the compiler does not result in all the possible optimizations in the code. A good practice would be to plan multiple compiler passes to achieve the maximum amount of code improvements. However, optimization is one of many desirable goals in software work product development and is often at odds with other important goals such as stability, maintainability, and portability. Optimization is usually beneficial when applied at its most cursory level (e.g., efficient implementation, clean non-redundant interfaces). But at its most intrusive (e.g., inline assembly, pre-compiled/self-modified code, loop unrolling, bit-fielding, superscalar, and vectorizing), it can be an expensive source of time-consuming recoding, recompiling, and bug hunting. Be cautious of the cost of optimizing your code.
Checklist of C programming practices for safety
Derived from NUREG/CR-6463, appendix B. Also, refer to a generic list of programming practices for safety—updated 10/21/2020 by the NASA Software Safety Guidebook Team.
- Limit the number and size of parameters passed to routines. Too many parameters affect the readability and testability of the routine. Large structures or arrays, if passed by value, can overflow the stack, causing unpredictable results. Always pass large elements via
- Use recursive functions with great care. Stack overflows are common. Verify that there is a finite recursion!
- Utilize functions for boundary checking. Since C does not do this automatically, create routines that perform the same function. Accessing arrays or strings out-of-bounds is a common problem with unpredictable, and often major,
- Do not use the gets function or related functions. These do not have adequate limit checks. Writing your routine allows better error handling to be
- Use memmove, not memcpy. Memcpy has problems if memory.
- Create wrappers for built-in functions to include error
- If “if…else if…else if…” gets beyond two levels, use a switch…case. This increases readability.
- When using the switch…case, always explicitly define default. Do not omit the break.
- Initialize local (automatic) variable before first use. They contain garbage before explicit initialization. Pay special attention to pointers since they can have the most dangerous effects.
- Initialize global variables in a separate routine. This ensures that variables are properly set at warm.
- Check pointers to make sure they don’t reference variables outside of scope. Once a variable goes out of scope, what it contains is
- Only use setjmp and longjmp for exception handling. These commands jump outside function boundaries and deviate from normal control.
- Avoid pointers to functions. These pointers cannot be initialized and may point to non-executable code. If they must be used, document the
- Prototype all functions and procedures! This allows the compiler to catch errors rather than having to debug them at run-time. Also, when possible, use a tool or other method to verify that the prototype matches the
- Minimize interface ambiguities, such as using expressions as parameters to subroutines or changing the order of arguments between similar functions. Also, justify (and document) any use of functions with an indefinite number of arguments. These functions cannot be checked by the compiler and are difficult to
- Do not use ++ or – operators on parameters being passed to subroutines, creating unexpected side effects.
- Use bitmasks instead of bit fields, which are implementation
- Always explicitly cast variables. This enforces stronger typing. Casting pointers from one type to another should be justified and
- Avoid the use of typedef’s for unsized arrays. This feature is poorly supported and error-prone.
- Avoid mixing signed and unsigned variables. Use explicit casts when
- Don’t compare floating-point numbers to 0 or expect exact equality. Allow some small differences due to the precision of the floating-point.
- Do not compare different data types, such as comparing a floating-point number to an integer.
- Enable and read compiler warnings. If an option, have warnings issued as errors. Warnings indicate that the deviation may be fine but may also indicate a subtle
- Be cautious if using standard library functions in a multitasking environment. Library functions may not be re-entrant and could lead to unspecified.
- Do not call functions within interrupt service routines. If it is necessary to do so, make sure the functions are small and re-entrant.
- Avoid the use of the ?: operator. The operator makes the code more difficult to read. Add comments explaining it if it is
- Place #include directives at the beginning of a file. This makes it easier to know what files are included. When tracing dependencies, this information is
- Use #define instead of numeric literals. This allows the reader or maintainer to know what the number represents (RADIUS_OF_EARTH_IN_KM, instead of 6356.91). It also allows the number to be changed in one place if a change is necessitated.
- Do not make assumptions about the sizes of dependent types, such as int. The size is often platform and compiler.
- Avoid using reserved words or library function names as variable names. This could lead to serious errors. Also, avoid using names that are close to standard names to improve the readability of the source.
Additional guidance related to software coding may be found in the following related requirements in this handbook: