Codecamp 2019: Question everything: The art of reducing bugs by challenging assumptions – Part 3
Please read the previous parts:
How to read the code
Let’s see what we can do when we read the code.
How to read functions
The purpose is not to completely understand what the function does – unless we need to modify it. We just need to have an idea about what it does.
First of all, we’re looking at the function signature (what it’s the return type, and what are the parameters). And of course, the function name, which will give us an idea of what it does.
We can highlight on the parameters and on the return value, to see where they are modified, and after control flow (if else switch for..) – we’ll have some examples and we’ll see more clear about what i talk about.
What’s important to keep in mind – try reading from the end to the start.
How to read classes
When we are reading a class, we need to understand what role it has in the project – where are the class instances created, who has ownership on them, were are they sent as parameter or returned. We also need to take lifetime into account, if they are created with composition, or received on constructor by reference.
Afterwards, we need to check what the class does: check its public functions, what methods from the base class it overrides, and in what conditions the private members are modified.
How to read architecture
The architecture of a project or domain is usually found in the documentation, but we can also check some known design patterns, such as factory or observer.
Let’s go over a few examples. These examples are taken from the bitcoin project, which is on github, as open source.
I repeat, it’s not important tot fully understand the function, but to make an idea regarding what it does.
First step? Signature.
We have a static function called CreateWallet, so we can safely assume it creates an object. That can also be seen by the return type – the function returns a shared_ptr object. The parameters are sent as const, so they are read only, and since the function is static, we don’t need to worry about class members.
Looking from the end of the function, we see we return a wallet_instance, which is a named variable, most likely defined in the local scope of the function, so we can highlight it and see where it is used.
If we scan the function, we see we have an early retturn on top, we load something into the object, and make a configuration.
You can see that wee don’t really need to understand what’s going on there – the function returns an object.
First step? Function signature.
Function returning bool, called GetWallet something, so the return is somewhere else, that bool is for validation. The parameters are by reference non-const, so we can assume they are modified in the function.
Going to the end of the function, we see we return a named variable, we check where it’s set to true, and right above we see what we set in those out parameters.
Ok, we have a function with 200+ lines…scary. But we see that there’s a lot of if-elses.
Once we do a fold on level 2 (level 1 is the function name) we see something interesting:
We simply have a try-catch and inside we do something based on the key type. Parameters are non-const, so the function probably modifies them.
That’s the beauty of code style – we know that if it’s non-const reference, it’s an out object.
Ok, we have a function with 120 lines, but A LOT OF CODE.
The function returns a result, and at the end it’s result ok.
Anything interesting with this code? we see lot of returns.
Those are early-exits, when we receieve something we don’t expect.
We can assume that the function will receive good input, and simply ignore them.
So where’s the remaining code? At the end. That’s why we need to start from there.
What’s important is that we’re left with ~40 lines of functionality, and we removed (ignored) 2/3 of the function by simply assuming the input is correct and everything will work correctly.