Something that people new to programming (and possibly industry veterans occasionally) do is copy-paste a piece of code between two files when they need the code’s functionality elsewhere. With very simple cases, this is fine. Also sometimes we cannot afford the overhead of a function call, but we know our function won’t be inlined. Generally, if your function is too large to inline, the function call overhead shouldn’t be much compared to the function execution time. However, sometimes compilers decide that a function with very few instructions mustn’t be inlined because inlining the function will bloat the executable size.

Going back to literally copy-pasting code, if you can literally just stick the exact same code where you are pasting it, generally things will be fine if you so happen to copy exactly the right code correctly and you don’t need to change variable names. If you are beginning programming, you shouldn’t feel ashamed of this practice. I’m not trying to condemn people. You are likely unfamiliar with generics, abstract classes and interfaces. I was fortunate enough to have been advised to take courses in information system design, which were concerned not just with what these concepts referred to, but how they allowed for better code.

So let’s say you are a making a octree. You need to subdivide a node into 8 equally sized nodes.

Source: https://en.wikipedia.org/wiki/Octree

You could write the line for creating one child, copy it 7 times and tweak each line as necessary. This could work, if you manage to make no mistakes. There is an alternate way to do this.

Note: This isn’t really a case of “making the compiler copy-paste for you”. What is happening here leads into that idea. This piece of code shows something I call “knowledge recycling”. You know how to create a single node. How can you recycle that code, that “knowledge” and reuse it? In this case I realised all the children were the same size. The difference was the child node positions. I noticed that you took every combination of adding and subtracting half the node size from its centre, you got all the children’s positions. Instead of checking the node creation code 8 times, once for each node, I check it once.

Because humans are error prone and slow compared to machines, copy pasting code is error prone and slow. How can we make the compiler do the copy pasting for us? That is where things like generics, utility functions, inheritance and abstract classes come in.

With utility functions, once we have successfully written a method (usually static) that performs some kind of generally useful functionality, like serializing or deserializing a file, it is useful to write that function in a way that let’s use it in multiple contexts. Or perhaps we write a mathematical function that may be useful for other projects or subsystems. If the code was not in a method, put it in a method. If the method was an instance method, consider making it a utility method (unless there is some privacy/access concern).

With generics, we can write classes and functions that support many types. Generally, we simply impose some kind of contract on the type. This can be done by requiring that the type extends a class or implements one or more interfaces. In doing so, we can guarantee the compiler that whatever type our generic class/function must work with will have the required methods and/or data. The compiler can now take care of creating the needed code so that whatever type we pass in will work correctly assuming the generic class/function is written well and the type fulfils its obligations sensibly. Going back to the deserialization/serialization example, once we work out how to correctly deserialize and serialize a particular type we can create a generic function that deserializes any type T. Generally you’d pull this off just by replacing classname with T and making the method take a type parameter T.

Abstract classes and inheritance let us define the behaviour of a class and all classes that inherits from it, and also let us provide constraints on classes inheriting from the abstract class. By migrating generally useful functionality to an abstract/base class, all classes inheriting from that class have the same functionality without you copy pasting that code to each class that needs it. Also when you change the functionality of the abstract/base class, you update all classes inheriting from that functionality. By using an abstract class you can force inheriting classes to have certain functionality that cannot be defined for the base abstract class.

For example, you could decide that all your AI entities they will have the same Line of Sight mechanism. Once you’ve made sure this mechanism works, migrating your Line of Sight method(s) to a base class means that all your AI entities can have an identical working LOS system, reducing the work done and testing needed for every entity. If a entity can’t spot enemies you don’t have to worry whether the LOS system is broken. You can look for more mundane problems like their sword blocking their view because you forgot to let the AI see through it. On the other hand, if you want for all your AI entities to have an attack but you can’t provide a generic attack function, your abstract class can define a abstract Attack() method. If a child class does not implement Attack(), it cannot be instantiated. You couldn’t tell all your entities how to attack, but your knowledge on how to successfully do Line of Sight checks is now recycled for all your entities.

With inheritance it is important to remember that not all inheritance is good inheritance. Define the behaviour and data of a base class so that a class inheriting from it gets as little unnecessary/unrelated functionality and data as possible from the base class. Also, there is a principle to uphold called the Liskov substitution principle: if class B inherits from A, B should be able to be passed where an instance of A is needed without unexpected behavior. I do think this rule is a bit less important than the first one, simply because sometimes at points in our inheritance hierarchy we do need to override inherited behavior.

A final minor case of “knowledge recycling” exists in the form of using getters and setters over direct variable access. Apart from properties letting us look after privacy/security concerns, they generally let us make sure that certain steps are carried out whenever a variable is accessed.

Hopefully you’re familiar with these concepts. Now here’s the interesting thing: reusable code is a resource. You may not own a company. You may not have any staff or a wealth of hardware and software resources. But your reusable code? It’s a resource. It’s code which has at least some guarantees of working in other contexts. That code saves you time and testing. That theoretically saves you money. That reusable code is a physical representation of your knowledge and experience.

However, reusable code shouldn’t always be your top priority. Deadlines can and will become more important than perfect code. In certain situations trying to make your code nice and reusable can and will bog you down. Knowing when to go for the simpler one-off solution is something that I am gaining experience in just as much as I am gaining experience in writing better code.

So, if you are a student/learning programming online, when should you start trying to apply the above principles? Well it depends on how your learning program works, but I’d say you want wait until you are comfortable with an object orientated language to the point where you feel comfortable solving problems and implementing data structures from scratch. Take a look at information systems and system design if you can. Depending on how you practice your programming you may get to start practicing generating reusable code early, if your assignments/practice will share required functionality.