Lesson 4: Deduplication
DRY code only! We all know that duplicated code is generally bad, “Don’t Repeat Yourself” is a well-intended principle that is often mentioned in this regard.
Duplication leads to bugs of the sort “I fixed it over here, but forgot to over there.” Over time, the duplication might become varied slightly such that it’s not even clear which version is the correct version. Ideally, the duplicated code can be simply extracted to one function to be called from multiple places. However, sometimes fixing the duplication is not so simple or obvious.
For example sometimes functions can be generally the same, but internally call different functions, Here, we have two functions that are very similar. The duplication is because the important difference is at the inner most level, so extracting a function to do the common code isn't immediately obvious.
To avoid repetition, Pointers-to-member-functions are used. The code is first extracted to its own member function that takes a pointer to the member function of MyObject as a
that needs to be called at the inner level. Function1 and Function2 then become wrappers to the extracted generic function.* * * * *
In other instances, Functions can contain repeated blocks of code. Here, we have a class member function that has a small code block used very similarly four times. Extracting the repeated code block to a new
might not be wanted because it's considered overkill for a simple operation not used anywhere else.The important differences between the four uses is obscured by the implementation details. It's at risk of further duplication if the code blocks get subsequently modified. Using lambda expression, Move the duplicated code into a lambda expression to define in one place a callable object local to the function that can be reused each of the four times.
Here it's pretty messy, but that's just because it's C++. Almost every other language, Rust, Javascript, Python, Lua, OpenSCAD, all have full nested function support. This means you can define a function in another function, and even use variables local to that scope. C++, Go, and Java do a workaround where they make a lambda, and everything it needs is passed in. This is good for making threads as threads need to be given this information.
For compiled languages, nested function pointers are hard to give somewhere else, because sometimes they need those local variables. An example of doing this can be given in Javascript:
Easy for languages that run on candy, sunshine, butterflies, hopes, dreams, and lies, but for lower abstraction languages like C++, it is nessesary to differentiate from functions and functors. Having every function be a functor would be a waste of memory, but Javascript loves to waste memory so it works.
* * * * *
In this case, we have 2 very similar constructor overrides. This can be a problem because we now have 2 constructors to keep in sync.
Here, we have a property page class with two constructors. They differ in whether it's default constructed or given parameters.
Using delegating constructors, you can let the simpler default constructor delegate to the two-parameter constructor with appropriate initial values. One place to modify, one place to maintain.
Constructor overloading is basically function overloading from lesson 2. Here's a working example (one of the longest on the website) to show you how this is used with function overloading. Feel free to copy the code into your IDE to see what happens.
* * * * *
Another issue we can have is multiple overloaded functions that are the exact same in their implementation, and return the same type. The only thing that changes is the types of the parameters.
In comes the template function.
Template functions are like if the quality of life improvement of funciton overloading benefited the writer of a function. It might be a little less easy to read for those uninitiated, but for the initiated, and for the writer of the function, it is a lot easier than the alternative.
Template function only realistically apply if the compiler can just substitute "T" (or whatever you name it) with whatever you input.
In this program we have a function that returns if an object is odd.
The benefit to this is that we don't have to think about all the types of variables that could get inputted. Almost everything should go in.
Does this seem like bad practice? Ever heard of a thing called std::cout?
std::ostream uses templates internally to make their lives a little easier. It also explains why your life is made a little harder when you get
* * * * *
Conclusion
These are merely suggestions for minimizing duplication, But it's opinion-based and coding style is subjective. Readability, project coding conventions, are equally important.
REMEMBER!! The emphasis for reducing duplication is not at all for reducing lines of code, but for reducing the opportunity for bugs, and to make maintaining easier.
Don't Repeat Yourself!.