Tradeoffs for Pre and Post Conditions for Code Blocks
A weaker precondition or less preconditions for a code block will be more general and less restrictive. A stronger precondition or more preconditions results in more specific or guaranteed inputs to the code block.
A stronger postcondition or more postconditions offer better guarantees for the output of the code block, because they restrict the development of the code block with more specificity. A weaker postcondition or fewer postconditions offer less guarantees, but provides more room to change the implementation later.
Decision Tables and the Code Smell due to Boolean Blindness
Decision tables or Parnas tables is clear in table form, but their T and F outputs are not descriptive and results in boolean blindness:
|n%3|n%5| f(n) |
|---|---|---------|
| T | T | "Both" |
| T | F | "Three" |
| F | T | "Five" |
| F | F | "Nil" |
To remove boolean blindness in Haskell, we introduce pattern matching with types:
data ThreeFive = Both | Three | Five | Neither
f :: Int -> ThreeFive
f n | n%3 && n%5 = Both
| n%3 = Three
| n%5 = Five
| otherwise = Neither
Code Smells due to Linguistic Antipatterns
from https://www.linguistic-antipatterns.com/
- multiple methods with similar names or effects which are confusing
- fields or functions with names that do not match their specificity or generality
- functions with names strongly and wrongly associated with inapplicable specifications or properties
- functions with names suggesting a return type but there's no return value
- names of functions, parameters or fields do not match their types
- functions with names suggesting purity, but they actually have side effects
Quality through Representability and Validness
Representability in code is obtained by including only the necessary information and not represent things that don't exist in reality. Validness in code is achieved when different real world situations are clearly distinguishable in the code. The goal is SPOT or Single Point of Truth, where the data structure or system that is created has a one to one relationship with the real-world system, and quality is built into the code from the start instead of trying to test it in later. Testing quality in later means that quality is treated as an afterthought, and efforts to improve or fix the data structure or system are made after it has already been built.
Exhibit "Good Taste" when Coding
The crux of the “good taste” requirement is the elimination of edge cases, which tend to reveal themselves as conditional statements. The fewer conditions we test for, the better our code “tastes”.
Example to loop over every point in the grid and used conditionals to test for the edges:
for (r = 0; r < GRID_SIZE; ++r) {
for (c = 0; c < GRID_SIZE; ++c) {
// Top Edge
if (r == 0)
grid[r][c] = 0;
// Left Edge
if (c == 0)
grid[r][c] = 0;
// Right Edge
if (c == GRID_SIZE - 1)
grid[r][c] = 0;
// Bottom Edge
if (r == GRID_SIZE - 1)
grid[r][c] = 0;
}
}
Better version of the code with "good taste":
for (i = 0; i < GRID_SIZE; ++i) {
// Top Edge
grid[0][i] = 0;
// Bottom Edge
grid[GRID_SIZE - 1][i] = 0;
// Left Edge
grid[i][0] = 0;
// Right Edge
grid[i][GRID_SIZE - 1] = 0;
}
Tradeoffs for DRY or Modularisation vs Coupling
DRY or Don't Repeat Yourself or modularisation reduces code bloat, but this may result in more coupled code, especially if there are cross dependencies between modules. When the software design is apparent or clearly represented in the code, and its structure subsetted hierarchically, there will be less coupled code. It is often not possible or practical to completely remove coupling from code, because some level of coupling and necessary dependencies are necessary for different parts of a software system to communicate and interact with each other. The goal is to minimise and manage this coupling to create a more maintainable and flexible codebase.