Maintenance
- Stong Cohesion/ Loose Coupling
- Check for bad input
- Data Scope
- Logging
- Documentation
- Design for Change
- Incremental Refinement
Software Lifecycle
- 60%+ of the work in development is applied after release.
- Most software engineering focuses on the high level (first) design of the software.
Review
- Requirements
- Design
- Implementation
- Testing
- Installation
- Maintenance
Post-Release
- Better to deal with bugs early in development; consider how to try to avoid them
After the first release, lifecycle focuses on either fixing bugs:
- Debug ⇆
- ⇆ Test
- Profile
- Optimize
… or adding new features:
- Add Feature
- Test ⇆
- ⇆ Debug
Defensive Programming
- A proactive approach to avoid errors when writing code.
- Respond to errors once the code is written.
- Other methods such as refactoring are reactive.
Cohesion & Coupling
Cohesion
[See managing complexity note]
- Components should focus on a single, well-defined task.
- Minimize side-effects (ideally have none).
- Weak cohesion is associated with functions that perform multiple, unrelated tasks.
Coupling
[See managing complexity note]
- Components should not rely too heavily on one another.
- Loosely coupled components are like building blocks and can be mixed and matched as needed
- High coupling means you cannot use one component without the other.
- Changes made in one component are more likely to affect others.
Mediators
Mediator: invokes a function indirectly through another function.
- Collects operations into a single module/class and provides an interface for both calling and receiving function.
- If either calling or receiving function change then the mediator isolates the change.
- Combines related functions and buffers the effect of change.
- Can use mediators for looser coupling.
Bad Input
- Assume that other programmers won’t use your code correctly.
- Don’t assume preconditions will be respected; check.
Example 1 (Bad)
float average(float *array, int size)
{
float sum=0;
int i;
for(i=0; i<size; i++)
sum = sum + array[i];
return(sum/((double) size));
}
Example 2 (Better)
float average(float *array, int size)
{
float sum=0;
int i;
if (size ==0)
return(DIV_BY_ZERO);
for(i=0; i<size; i++)
sum = sum + array[i];
return(sum/((double) size));
}
Bad Arguments
- NULL pointers (when not expected).
- Easy to test.
- Dangling pointers.
- Must be careful (disciplined) in allocating/freeing memory.
- Out-of-range values.
- Usually easy to test.
- Incorrect data type.
- Incorrect amount of data; ie. buffer overflows.
- If done accidentally program will behave unpredictably or seg. fault (memory corruption).
- Buffers overflows are a commonly exploited security issue.
- If “code” is inserted correctly, it can be executed.
C functions to avoid
strcpy
(usestrncpy
).strcat
(usestrncat
).gets
(usefgets
).
Data Scope
- Global variables make it harder to understand the program.
- Constantly scrolling through code to find which variables are being used.
- Worse in languages that don’t have local variables (COBOL).
- Only variable naming convention can be used to identify what is going on.
- Global variables destroy modularity; source of coupling.
- Defensive programming implies that you will minimize the scope of variables (as much as is possible).
- To modify code using global variables you need to keep in mind all of the other places in the code where it is used (often impossible in large code base).
- Solution is often to rewrite the code.
Logging
Uses in Defensive Programming
- Used to capture program events.
- Used to trace program execution.
Log Messages
- Indicates where an event occurred.
- Severity of event.
- Message itself.
Documentation
Incorrect Documentation
- Caused when code is updated and the changes are not documented.
- Result is useless documentation.
- Cannot tell which documentation is correct, so can’t trust any of it.
- Solution is disciplined documentation.
Too Much Documentation
- When amount of documentation is far greater than code.
- So much to maintain over time leads to it becoming less correct as time passes.
Absent Documentation
- Every operations involves learning how that part of the system works (from scratch).
- Tremendous amount of time is wasted relearning the system.
Intention
- Explaining why you chose to do things.
- Can get excessive and lead to superficial comments.
- If it is obvious how the code is functioning then explaining it in words will not help.
- If you explain why you chose to do it that way the programmer will understand what you were thinking.
- Self-documenting code means no documentation.
- Code should explain itself.
- Can be difficult to do and deteriorates under change.
Incremental Refinement
- Not all maintenance can be done in one attempt.
Unit Testing
- Done over a short period of time and is applied to a small part of the system.
- Individual modules/ADTs, etc.
- Normally involves automated testing.
- Output is limited to short, post test summaries.
- Error information is redirected to a file.
- Configuration does not require compilation; use configuration files.
- Testing process should only produce a reaction if something fails.
- Tests can be run automatically when code is compiled or more systematically.
- Consider the value of regression testing in this context.
- Don’t confuse this with system testing which is done once the program is complete.
- This can take weeks of time and involves the entire system.
Machine Dependencies
- These can be subtle bugs because people often don’t suspect the hardware (or worse, don’t understand how it can impact their software).
- In the past, hardware was more suspect and could cause problems.
Endianess
Endianess refers to the order in which the bytes in a word are organized.
- This is a problem when you transfer data between two machines with different endianess (raw values, files, etc).
- Can convert all data when read (assuming you know the relevant conventions).
Memory Alignment
- Based on the CPU/hardware requirements
- Indicates where variables will start in memory.
- Variables will start on either 2, 4 or 8 bytes boundaries (sometimes even larger depending on memory architecture).
- Variables in structures will take more space than you might expect.
- File will be larger than the minimum size.
- C standard does not define the size of data types; use
sizeof
.
Virtual Machines
- Using a VM (such as JVM: Java Virtual Machine) avoids many of the machine dependency problems.
- VM provides a constant endianess, data size and memory alignment regardless of underlying hardware on which it runs.
- As long as the VM follows the standard, the code will run anywhere.
- Porting is a non-issue.
- Comes with a price.