“Team-friendly” code

Hello! So you are interested in making your project more team-friendly? There are several ways to achieve that and what I will show you are a few ways that worked with my team. There is no “right way”, there is just the way that fits better for your team. Actually, many people from your team may not adapt to these methods, and it is up to you to identify what is better for your project.
The responsibility of implementing a code pattern changes often between companies. Some of them leave the responsibility to a senior developer, others give the task to a code-reviewer or a tech-lead. It doesn’t matter what your role is, implement a code pattern is an arduous task. There will always be people that won’t accept the method or won’t understand its value for the project as a whole. Some will even consider that you are cutting down their creativity.
You, as a person who is responsible for the code health, sometimes have to think about the good of the project or the company. That means much more than code sometimes. You have to think about new developers being hired, training, etc. The more your code is friendly to a new team member the easier it will be for you to grow your team with less time spent on training.
TRIGGER WARNING: If you are a developer that does not like being told how to code, you better stop here. 🙂
To understand everything I’ll show you, try to consider the view of a new person on the project, especially a less experienced one.
We all ben there: A new developer joins the team and no one has the available time to do the training. New developers gets unmotivated because they want to create and feel useful. It may lead to other team members looking down on them as if they were not doing their job. How do you solve that? Well, for that I have 2 motos: “Code to others to read, not yourself.” and “Use the IDE as documentation”.
Documentation by variable names
Consider a very common case. You have a function that has a few arguments in a specific order.
# sentences.py def explain_lunch(a, b, c): print(f"{a} cooked {b} and {c} for lunch.")
As you can see, the function is part of a module “sentences.py”. Usually, the code of this module is not being seen by who is importing it. A new team member on the project that is importing this function has no idea what it does. Let’s see what happens when using the function on an IDE like PyCharm and let the documentation popup appear.
The popup is not very helpful, right? What is “a, b, c”? That question will force the developer to open “sentences.py” and look at its code. It may sound very simple but it takes a lot of time, even more, if we have hundreds of functions like that.
How can we improve that? Let’s start by just renaming the variables. This is what any “good practices” tutorial out there will already teach you.
# sentences.py def explain_lunch(name, food1, food2): print(f"{name} cooked {food1} and {food2} for lunch.")
Well, now the popup is a little more helpful.
Seeing this popup, new team members will know right away that they have to pass a name and two types of food as arguments without having to look at the function’s code. Their code will look like this:
explain_lunch("Mary", "rice", "beans") # Mary cooked rice and beans for lunch.
But now, consider that you are another developer that is reading the line of code above. With only that line, there’s no way for this developer to know what “Mary”, “rice” and “beans” represent.
The IDE is good at helping who is writing the code, but it doesn’t help who is reading it that much. For example, if you opened that little piece of a code on a common text editor, you would have no idea of what those 3 arguments represent unless you, once again, open “sentences.py”.
How do you improve that? Well, this does not depend that much on the IDE. It’s a change in how you code and how you think. You gotta think about the experience of whom is reading your code, not yours. You will always understand that you have written but that might not be true for somebody else, especially if that person is less experienced.
Python has a feature where your function arguments can be named. This helps reading and understanding your code as soon as you lay eyes on it:
explain_lunch(name="Mary", food1="rice", food2="beans") # Mary cooked rice and beans for lunch.
– But that is too much work!
– Yep.
– But it gets too verbose!
– Exactly.
– My devs won’t like it!
– Most certainly!
– Then why do it?
– The next dev that reads that line already has all information he needs to run that function. He already knows the names of the arguments and even has an example of data to send to them.
– I am still not sold. If the devs have to type more, how am I not losing productivity?
– Simple. Devs come and go from your project, your code remains. If you want to keep the maintenance of your code simple or add more people to your team with less training time you gotta worry about your code more than personal preferences.
75% of the devs with less than 10 years experience will have a negative reaction to the paragraph above.
Source: Guess-o-meter
Change in mindset didn’t work? –force
Sometimes you face many egos in this area. There will always be developers that won’t work the way you want and on every code-review, you will see them doing the exact opposite. In Python, there is a nice way to “enforce adoption” 😉 with the “*” as an argument.
def explain_lunch(*, name, food1, food2): print(f"{name} cooked {food1} and {food2} for lunch.")
With a simple * on the function arguments, you require that all arguments after that have to be named. Check what happens when you enforce that configuration. The IDE itself will help you “convince” your stubborn dev.
Now all the calls to that function have to use named arguments. It’s pretty certain that the developers that are not willing to change their ways will complain about this method but it will certainly make hiring new devs easier. /wink 😉
EXTREME DOCUMENTATION MODE!!!
Whoa, are you still there? You must like documentation and is not afraid of tears! So, let’s change our simple function to its final form.
def explain_lunch(*, name: str, food1: str, food2: str): """ Prints a sentence explaining someone's lunch. :param name: name of the person :param food1: some food that the person cooked that day :param food2: another food that the person cooked that day """ print(f"{name} cooked {food1} and {food2} for lunch.")
Now we are using 2 additional types of documentation. The traditional code commentary and type hinting.
So, what are the advantages? The traditional documentation on a function’s body is identified by most IDEs these days and shown automatically when the mouse is over the function’s name. This is a great help to avoid the dev navigating through files to make sense of the function.
Besides that, type hinting also helps the developer understand what type of argument he has to pass to the function. Some IDEs even complain if the type is incompatible.
See this documentation? Much better, right? With these simple steps, new team members have all the information they need to use a function without ever leaving the file he is editing. (VIM people heavy-breathing).
Classes aka Namespacing
There is always this tiring argument about object-oriented programming vs functional programming. I am adept at OO most of the time and I tend to stay away from these arguments. In Python, I like to use classes as some sort of namespace to group functions with similar goals together and make importing easier to remember.
Let’s say you have a module that deal with strings, like this:
# my_utils.py def to_uppercase(text: str): pass def to_lowercase(text: str): pass def to_foo(text: str): pass def to_bar(text: str): pass
If you need to import those functions, you will have to do it like this:
from my_utils import to_uppercase, to_foo, to_bar
If you consider new team members, without an analysis of the my_utils module, they would have no idea what is available for importing.
A method that can help you group those functions together is to use classes as namespaces:
# string_utils.py class StringUtils: @staticmethod def to_uppercase(text: str): pass @staticmethod def to_lowercase(text: str): pass @staticmethod def to_foo(text: str): pass @staticmethod def to_bar(text: str): pass
What is the difference? Every developer will answer this question differently. There will be endless technical and even ideological opinions about it. In my experience, the gains outweigh the losses if you consider the prism of using the IDE as a documentation and training tool. Here is what “importing” all those functions would look like:
from string_utils import StringUtils as su print(su.to_lowercase("HELLO"))
You see no advantage? You won’t be the only one, trust me…
But take a look:
As you can see, by using a class to group all those functions in a namespace you don’t need to remember every single function name to import. You only need to remember the namespace, or service layer, as you may.
Again, many developers will have different opinions about this. It’s up to you to decide with your team what is best. Either way, always remember to put the project first so you can have an easy administration of your codebase. Devs come and go…