Coding Standards For Functions
From Wiki
Contents |
Standards For Functions/Operations
An application spends the majority of its existence being maintained, not developed. For this reason, anything that can help to make code easier to maintain and to enhance, as well as to develop it is useful. Never forget that the code that you write today may still be in use many years from now and will likely be maintained and enhanced by somebody other than you. You must strive to make your code as "clean" and understandable as possible, because these factors make it easier to maintain and to enhance.
Naming Functions/Operations
Functions should be named using a full English description, using mixed case with the first letter of any non-initial word capitalized. It is also common practice for the first word of a member function name to be a strong, active verb.
// Naming Functions/Operations openAccount() printMailingLabel() save() delete()
This convention results in functions/operations whose purpose can often be determined just by looking at its name. Although this convention results in a little extra typing by the developer, because it often results in longer names, this is more than made up for by the increased understandability of your code.
Naming Accessors
Getters are functions that return the value of a field. You should prefix the word get to the name of the field, unless it is a boolean field and then use prefix is to the name of the field instead of get.
//Getters getFirstName() getAccountNumber() getLostEh() isPersistent() isAtEnd()
A viable alternative, based on proper English conventions, is to use the prefix has or can instead of is for boolean getters. For example, getter names such as hasDependents() and canPrint() make a lot of sense when you are reading the code.
Setters, also known as mutators, are member functions that modify the values of a field. You should prefix the word set to the name of the field, regardless of the field type.
setFirstName(aName) setAccountNumber(anAccountNumber) setReasonableGoals(newGoals) setPersistent(isPersistent) setAtEnd(isAtEnd)
Notice that in PHP variables must start with dollar $.
Lazy Initialization
Variables need to be initialized before they are accessed. There are two lines of thought to initialization:
- Initialize all variables at the time the object is created (the traditional approach) or
- Initialize at the time of first use.
The first approach uses special member functions that are invoked when the object is first created - constructors. Although this works, it often proves to be error prone. When adding a new variable you can easily forget to update the constructor(s). An alternative approach is called lazy initialization where fields are initialized by their getters, as shown below.
Below is a PHP example:
/**
Answers the branch number, which is the leftmost
four digits of the full account number.
Account numbers are in the format BBBBAAAAAA.
*/
function getBranchNumber(){
if( 0 == $branchNumber){
// The default branch number is 1000, which
// is the main branch in downtown Bedrock.
setBranchNumber(1000);
}
return $branchNumber;
}
Whenever lazy initialization is used in a getter you should document why the default value is what it is, as we saw in the example above. When you do this you take the mystery out of how fields are used in your code, improving both its maintainability and extensibility.
Accessors for Collections
The main purpose of accessors is to encapsulate the access to fields so as to reduce the coupling within your code. Collections, such as arrays, being more complex than single value fields naturally need to have more than just the standard getter and setter implemented for them. In particular, because you can add and remove to and from collections, accessors need to be included to do so.
| Getter for the collection | getCollection()
| getOrderItems()
|
| Setter for the collection | setCollection()
| setOrderItems()
|
| Insert an object into the collection | insertObject()
| insertOrderItem()
|
| Delete an object from the collection | deleteObject()
| deleteOrderItem()
|
| Create and add a new object into the collection | newObject()
| newOrderItem()
|
Visibility of Accessors
Always make them protected, so that only subclasses can access the fields. Only when an "outside class" needs to access a field should you make the corresponding getter or setter public. Note that it is common that the getter be public and the setter protected.
Sometimes you need to make setters private to ensure certain invariants hold. For example, an Order class may have a field representing a collection of OrderItem instances, and a second field called $orderTotal which is the total of the entire order. The $orderTotal is a convenience field that is the sum of all sub-totals of the ordered items. The only member functions that should update the value of $orderTotal are those that manipulate the collection of order items. Assuming that those functions are all implemented in Order, you should make setOrderTotal() private, even though getOrderTotal() is more than likely public.
Why to use accessors?
Kanerva (1997) says: <q>"Good program design seeks to isolate parts of a program from unnecessary, unintended,or otherwise unwanted outside influences. Access modifiers (accessors) provide an explicit and checkable means for the language to control such contacts." <q> Accessors improve the maintainability of your classes in the following ways:
- Updating fields. You have single points of update for each field, making it easier to modify and to test. In other words your fields are encapsulated.
- Obtaining the values of fields. You have complete control over how fields are accessed and by whom.
- Obtaining the values of constants and the names of classes. By encapsulating the value of constants and of class names in getter when those values/names change you only need to update the value in the getter and not every line of code where the constant/name is used.
- Initializing fields. The use of lazy initialization ensures that fields are always initialized and that they are initialized only if they are needed.
- Reduction of the coupling between a subclass and its superclass(es). When subclasses access inherited fields only through their corresponding accessor member functions, it makes it possible to change the implementation of fields in the superclass without affecting any of its subclasses, effectively reducing coupling between them. Accessors reduce the risk of the "fragile base class problem" where changes in a superclass ripple throughout its subclasses.
- Encapsulating changes to fields. If the business rules pertaining to one or more fields change you can potentially modify your accessors to provide the same ability as before the change, making it easier for you to respond to the new business rules.
- Name hiding becomes less of an issue. Although you should avoid name hiding, giving local variables the same names as fields, the use of accessors to always access fields means that you can give local variables any name you want – You do not have to worry about hiding field names because you never access them directly anyway.
The Function Header
Every function should include some sort of header, called function documentation, at the top of the source code that documents all of the information that is critical to understanding it. This information includes, but is not limited to the following:
- What and why the function does what it does. By documenting what a function does you make it easier for others to determine if they can reuse your code. Documenting why it does something makes it easier for others to put your code into context. You also make it easier for others to determine whether or not a new change should actually be made to a piece of code.
- What a member function must be passed as parameters. You also need to indicate what parameters, if any, must be passed to a member function and how they will be used. This information is needed so that other programmers know what information to pass to a member function. The
@paramtag must be used. - What a member function returns. You need to document what, if anything, a member function returns so that other programmers can use the return value/object appropriately. The
@returntag must be used for this. - Known bugs. Any outstanding problems with a function should be documented so that other developers understand the weaknesses/difficulties with the function. If a given bug is applicable to more than one function within a class, then it should be documented for the class instead.
- Any exceptions that the function throws. You should document any and all exceptions that a member function throws so that other programmers know what their code will need to catch. The
@throwstag must be used for this. - Visibility decisions. If you feel that your choice of visibility for a function will be questioned by other developers, perhaps you've made a member function public even though no other objects invoke the member function yet, then you should document your decision. This will help to make your thinking clear to other developers so that they do not waste time worrying about why you did something questionable.
- How the function changes the object. If the function changes the object, for example the
withdraw()function of a bank account modifies the account balance then this needs to be indicated. This information is needed so that other programmers know exactly how a member function invocation will affect the target object. - Include a history of any code changes. Whenever a major change is made to a member function you should document when the change was made, who made it, why it was made, who requested the change, who tested the change, and when it was tested and approved to be put into production.
- Examples of how to invoke the member function if appropriate. One of the easiest ways to determine how a piece of code works it to look at an example. Consider including an example or two of how to invoke a member function.
- Applicable preconditions and postconditions. A precondition is a constraint under which a function will work properly, and a postcondition is a property or assertion that will be true after a function is finished running (Meyer, 1988). In many ways preconditions and postconditions describe the assumptions that you have made when writing a member function (Ambler, 1998), defining exactly the boundaries of how a member function is used.
See also: PHPDoc, Javadoc, JSDoc
Internal Documentation
In addition to the function documentation, you also need to include comments within your member functions to describe your work. The goal is to make your member function easier to understand, to maintain, and to enhance. Internally, you should always document:
- Control statements. Describe what each control statement does, such as conditional statements and loops. You shouldn't have to read all the code in a control statement to determine what it does, instead you should just have to look at a one or two line comment immediately preceding it.
- Why, as well as what, the code does. You can always look at a piece of code and figure out what it does, but for code that isn't obvious you can rarely determine why it is done that way. For example, you can look at a line of code and easily determine that a 5% discount is being applied to the total of an order. That is easy. What isn't easy is figuring out why that discount is being applied. Obviously there is some sort of business rule that says to apply the discount, so that business rule should at least be referred to in your code so that other developers can understand why your code does what it does.
- Local variables. Each local variable defined in a function should be declared on its own line of code and should usually have an endline comment describing its use.
- Difficult or complex code. If you find that you either can't rewrite it, or do not have the time, then you must document thoroughly any complex code in a function. The general rule is that if your code isn't obvious, then you need to document it.
- The processing order. If there are statements in your code that must be executed in a defined order then you should ensure that this fact gets documented (Ambler, 1998). There's nothing worse than making a simple modification to a piece of code only to find that it no longer works, then spending hours looking for the problem only to find that you've gotten things out of order.
Local Variables
A local variable is an object or data item that is defined within the scope of a block, often a function. The scope of a local variable is the block in which it is defined.
Naming Local Variables
In general, local variables are named following the same conventions as used for fields, in other words use full English descriptors with the first letter of any non-initial word in uppercase.
Naming Loop Counters
Because loop counters are a very common use for local variables, and because it was acceptable in C/C++ and Java, the use of i, j, or k, is acceptable for loop counters. If you use these names for loop counters, use them consistently. A major disadvantage of using single letter names for counters, or for anything, is that when you try to search for its use within a code file you will obtain many false hits – consider the ease of searching for myCounter over the letter i.
Declaring and Documenting Local Variables
There are several conventions regarding the declaration and documentation of local variable in Java. These conventions are:
- Declare one local variable per line of code. This is consistent with one statement per line of code and makes it possible to document each variable with an endline comment (Vision, 2000).
- Document local variables with an endline comment. Endline commenting is a style in which a single line comment, denoted by
//, immediately follows a command on the same line of code (this is called an endline comment). You should document what a local variable is used for and where appropriate why it is used, making your code easier to understand. - Declare local variables immediately before their use. By declaring local variables where they are first needed other programmers do not need to scroll to the top of the member function to find out what a local variable is used for. Furthermore, your code may be more efficient because if that code is never reached the variable will not need to be allocated (Vision, 2000). The main disadvantage of this approach is that your declarations are dispersed throughout each of your member functions, making it difficult to find all declarations in large member functions.
- Use local variables for one thing only. Whenever you use a local variable for more than one reason you effectively decrease its cohesion, making it difficult to understand. You also increase the chances of introducing bugs into your code from the unexpected side effects of previous values of a local variable from earlier in the code. Yes, reusing local variables is more efficient because less memory needs to be allocated, but reusing local variables decreases the maintainability of your code and makes it more fragile. This usually isn’t worth the small savings from not having to allocate more memory.
Parameters
Naming parameters
Parameters should be named following the exact same conventions as for local variables. As with local variables, name hiding is an issue (if you aren't using accessors).
A viable alternative, taken from Smalltalk, is to use the naming conventions for local variables, with the addition of a or an on the front of the name. The addition of a or an helps to make the parameter stand out from local variables and fields, and avoids the name-hiding problem.
Another alternative to naming variables is to give them a name based on their type. However, a parameter name like aString tells you much less information than a name like accountNumber orfirstName.
Documenting Parameters
Parameters to a function are documented in the header documentation for the function using the@param tag.
