Welcome back! We’re going to get into how to write functions in this section of the collaborative coding guidelines - refer back to the previous article for the guidelines on variables. Let’s dive right in!
Part II: Functions
Access Modifiers - Conventions and Intention
In C#, the convention is that function names should be capitalized in camel case - like ThisIsAFunctionName(). Unless you have a very good reason to not use this convention, you should always do this. In addition, the same conventions we used for scope naming for variables, we’re going to use for functions - private functions are preceded by an underscore. Although this isn’t a C# coding standard, I’ve found it makes it very easy for my collaborators to know which parts of my code are public and private at a glance.
Guideline: Function names need to be in PascalCase - first letter capitalized, and the first letter of each “word” capitalized. Private function names should be preceded by an underscore.
Just like with variable access modifiers, the access of your functions is one of the clearest ways you can communicate your intention to your collaborators. If you have a private function, it’s clear that it’s not meant to be accessed from outside of the class, and using it may make the rest of your code not work correctly.
Guideline: Use access modifiers to communicate your intention.
Using Return Values
Using functions with return values is a powerful way to provide access to the guts of your classes, and to make your code much more readable. Say, for example, you want to see if a skill is available, by checking if it was chosen by the player, and if the time-out for the skill is through. You could write that into the conditional:
This is a bad idea for three reasons:
It’s unclear. It’s not obvious that you’re checking if the current skill is available, instead, it looks like two separate checks.
It’s hard to reuse. If at other points in the code, you want to check if a skill is available, you’ll have to copy and paste this same conditional. You want your code to be as reusable as possible.
It’s difficult to change. If you decide later to change how a skill’s availability is determined, you’ll need to remember where you did this check (and change it everywhere you checked it). This is an easy failure point to have bugs and issues.
Instead, you want to encapsulate the conditional into a function that returns a bool. Suddenly, your code will be much more readable and clearer, and it’s reusable everywhere you need to do the check. Plus, making changes to the logic of your game is easy, because it’s labeled and organized.
This is also a good tactic to use for any complicated logic you have in your game, not just conditionals. Using a function like GetDamage(enemy.PrimaryAttack), for example, is better than calculating that damage inline.
Guideline: Break conditionals or complicated logic out into functions with return types.
Note: If you’re more advanced, you should replace this guideline w/using properties or predicates, however this guide is aimed at intermediate gameplay programmers.
Frame Functions Positively
Also, it’s better to frame your function names positively instead of negatively. Using the ! symbol to flip a boolean is much better and clearer, and the logic of the function will be easier to update and understand. I’ve often seen negatively framed return values accidentally use the “&&” operator instead of the “||” operator and fail to work correctly.
Guideline: Frame your functions positively instead of negatively.
Name Based On Function
Another thing I’ve often seen are descriptions of the implementation as a function name. Much like with variable naming, it can be easy while you’re coding to describe what’s happening in a function inside it, but it’s always better to describe what the function does, instead of how it does it. It’s much clearer for your collaborators (and the future version of yourself!) to understand.
Guideline: Name a function based on why you need it and what it does, rather than how it does it.
And if you’re having a hard time determining what the function of a function is...
Single Responsibility Functions and Short Functions
You want each function to have only one responsibility. If you can’t express the one thing a function does, you likely have too much happening inside the function, and it’s better to break the function into smaller sub-functions that are called from the main function. You want to avoid making long functions with a lot of logic in them - they’re difficult to read and digest what is happening within them. It’s much easier to read a series of well-encapsulated function calls with good names.
As a general rule, I tell my students that most functions shouldn’t be longer than ten lines, and if they have a longer function, they need to revisit it and make sure it’s only implementing one responsibility. I know that number is a little arbitrary, and no one fails if they have a function that is eleven lines, but it’s a helpful check to see if the function adheres to the single responsibility principle.
Guideline: No function should have more than one responsibility - and keep it short. If it is longer than ten lines, check that you shouldn’t break it into separate functions.
Finally, it’s important to have no repeated code. None - no functionality should be in multiple places, even if it’s slightly different. You should always put that code together into a function and call it wherever you need it. Not only does this clean up your code and make it easier to understand, it makes it easier for your collaborator to make changes without having to look at all the code to find every place you used the code you had copied and pasted.
Sometimes it can be less clear about how to collapse repeated code into one function - usually, adding a parameter can make that collapse much cleaner.
Guideline: Have no repeated code. Instead, collapse that code into one function, using a parameter if necessary.
That’s it for how to structure your functions and name them to be as clear as possible. In the next section, we’re going to go into how to organize your code logically to make it as easy as possible for your collaborators to interact with and contribute to.