James Course - Future-Proofing Code Flashcards
What are the two competing narratives in software engineering?
There is the first line that says, you should go fast, over-engineering is bad, don’t plan for a future that will never come.
The other one is, you have to get it right, or you will be in a huge technical debt.
Neither one is the whole truth. This week is about the factors that influence some decisions to be harder to change than others and what you can do about them.
What are the two types of bugs mentioned in the class and how they differ?
The first one is a bug in the randRange function that was a bug in the implementation. It could be fixed just by changing the implementation and nothing else would need to be done.
The other one is the referer bug in the HTTP spec, and to fix that one we would need to change all server implementations, redeploy all servers, then we can change all clients, and redeploy all clients. That will never happen.
What are the 3 steps to produce future proof code?
Increase flexibility by:
- Reduce and sequester assumptions
- Add openness
- Diminish complexity ratches
What was the example used to illustrate assumptions in code, what are them, and why is bad?
The computeDamage function.
The assumptions were:
- Damage is only computed by two ints;
- The two ints are positive;
- Damage is not computed by floats;
Assumptions propagate up the call stack. All callers will have this same assumption.
In the second example the attributes were grouped in an AttackerInfo and a Creature object. The assumptions, that were weaker, are:
- AttackInfo has a baseDamage
- Creature has a multiplier
- computeDamage don’t depend on a Defender, is a function of AttackInfo and Creature.
When it would make sense to pass a parameter that you never use in a function?
When there is the possibility to extend this function to use the parameter, and this function is going to be called a thousand times.
It is like a lawyer saying: I reserve the right to use this even if I have no plans to do so in the future.
What are some recommendations for reducing assumptions?
To reduce your assumptions you need to know what they are.
- Use the Embedded Design Principle
- Pay attention to function prototypes, ask what assumptions it has and what it knows. Remember about the Hoare Triples class.
- Pass around records over primitive types, it makes it easier to add more stuff later.
What is the second recommendation to make code future proof?
Add openness.
Remember about the TodoItem privacy setting, one way to do it is to just use an attribute isPrivate, the other one is to use refunctionalization to create a PrivacySetting that has a canView method. In the second way the TodoItem does not need to know that there are more privacy settings, adding openness to it.
Conditional to subclass transformation is a common way of getting openness.
Records used in the examples of reducing assumptions also helps to add openness, because you can add more thing to records without changing the program at all.
What is the third way of making code future proof?
Diminish complexity ratches.
A complexity ratch is a decision you make that makes your code more complex and where this complexity cannot be taken back.
Remember about the map version problem where they had to put a magical -1 in the width to identify that this was a newer map version.
What is a possible solution to the map version problem?
They could use a JSON record format instead of a raw binary one.
This way they could’ve added extra fields for the new features without changing the format at all. Not only that, but they would actually get some forward compatibility. Where the original game could read from a newer map version record, and simply ignore the new features without any change in the code.
Why the record format is better than the raw byte format?
Because of the Liskov Substitution Principle:
T is a subtype of U if every value of type U in a program can be replaced with T and the program will continue to function.
How can we apply sub-typing to files?
Files are values. File formats are types (shapes of data).
If file format fmt1 is a subtype of fmt2, then any program that reads fmt2 can also read fmt1 with no change in code.
How can we apply sub-typing to functions?
Functions are values. Their signatures are types.
If function f1 has a subtype of that of function f2, then f2 can be replaced with f1, and the rest of the program will continue to work.
What is subtyping about?
Is not about classes and subclasses, it’s more about the fundamental fact of how we can evolve code without changing things.
What is subtyping?
A is subtype of B iff A is more specific than B.
Notation A < B.
We can think of subtyping as in a ven diagram where A is inside a set B.
What are the laws of subtyping for products?
A x B < A’ x B’ iff A < A’ and B < B’
or
A x B < A always
intuition: You can
- Make fields more specific
- Add fields