Cele mai bune practici în scrierea codului

  1. Cod curat
  2. Nume adecvate
  3. Funcții
  4. Comentarii
  5. Formatare
  6. Testarea codului
  7. Rezumat

Cod curat

Ce este codul curat? Codul curat poate fi citit și îmbunătățit de către un programator, altul decât autorul original.

De ce este important să scriem cod curat?

Metafora geamurilor sparte

O clădire care are geamuri sparte arată de parcă nimănui nu îi pasă de ea. Deci nici altor persoane nu le mai pasă și permit ca alte geamuri să fie sparte. Eventual, se vor sparge și ele.

Acest lucru se aplică și în cod.

Nume adecvate

Nume există oriunde în cod. Trebuie să îți denumești:

  • variabile
  • funcții
  • argumente
  • clase
  • fișiere sursă etc.

Pentru că tu faci cele mai multe, trebuie să le faci bine.

Ce urmează sunt reguli simple de a crea nume bune.

Folosiți nume care să evidențieze intenția

Alegerea numelor bune durează dar va salva mult mai multe lucruri pe viitor.

Schimbați-le atunci când găsiți altele mai bune.

Numele ar trebui să răspundă la întrebările importante:

  • de ce există?
  • ce face ?
  • cum este folosit ?

Dacă un nume are nevoie de un comentariu, atunci numele nu își dezvăluie de fapt atenția.

Exemplu:       în loc de :

int d; // timp trecut în zile

folosiți :

int timpTrecutInZile;

Evitați dezinformarea

Evitați cuvinte ale căror înțeles poate să varieze.

hp, aix, și sco pot fi dezinformative deoarece sunt nume de pe platformele Unix.

Nu va referiti la grupuri de conturi ca fiind accountList, decât dacă este într-adevăr o listă.

Cuvântul „list” înseamnă ceva specific pentru programatori. În cazul în care containerul care are conturi nu este chiar List, false concluzii pot fi trase.

Atenție la nume care pot varia foarte puțin. Cât de mult v-a luat să vedeți diferențele dintre:

XYZControllerForEfficientHandlingOfStrings și

XYZControllerForEfficientStorageOfStrings

Nu schimbați un nume într-un mod arbitrat doar pentru a satisface un compilator sau interpretor.

Numirea seriilor de numere (a1, a2, .. aN) este opusul numirii intenționale.

Aceste nume nu sunt dezinformative – sunt noninformative.

Exemplu:

public static void copyChars(char a1[], char a2[]) {

   for (int i = 0; i < a1.length; i++) {
        a2[i] = a1[i];
   }
}

public static void copyChars(char source[], char destination[]) {
   for (int i = 0; i < source.length; i++) {
       destination[i] = source[i];
   }
}

Folosiți nume ce pot fi pronunțate

Oamenii sunt buni la cuvinte. O bună parte din creierul nostru este dedicat conceptului de cuvinte. Iar cuvintele sunt, din definiție, pronunțabile. Deci creați-vă nume pronunțabile.

Folosiți nume compuse din verbe si substantive decât formate din abrevieri.

Folosiți nume care pot fi căutate

Evitați să folosiți nume formate dintr-o singură literă (le puteți folosi doar ca variabile locale în metode scurte)

Lungimea unui nume trebuie să corespundă cu mărimea contextului său.

Exemplu:

int e = 0;

Folosirea variabilei e este greu de găsit.

Evitați folosirea constantelor numerice.

Este mai ușor să căutați folosința lui MAX_CLASSES_PER_STUDENT, decât folosința numărului 7, deoarece 7 poate avea mai înțelesuri diferite în contexte diferite.

codul sursă original :

for (int j=0; j<34; j++) {
    s += (t[j]*4)/5;
}

codul sursă refactorizat :

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
        sum += realTaskWeeks;
}

Evitați corelarea între numele variabilelor și cuvintele deja cunoscute

Cititorii nu trebuie să traducă mental nume în alte nume pe care le cunosc deja.

Evitați alegerea numelor de variabile precum: a, b, x1,y.

Un contor de buclă poate fi denumit i sau j sau k (dar niciodată l!) dacă contextul său este minimal și niciun alt nume nu are vreun conflict cu el. Acest lucru este valabil pentru că numele de o singură literă pentru contoarele de buclă sunt tradiționale.

În cele mai multe situații, numele formate dintr-o singură literă nu sunt o alegere bună.

Nu folosiți numele c, doar pentru că a și b au fost alese deja.

Nume de metode

Metodele ar trebui să conțină verbe, care să descrie acțiunea pe care trebuie să o execute.

Exemplu:  postPayment, deletePage, save, create, updateCustomer.

Variabilele de acces trebuie denumite după valoarea lor și prefixat cu get, set.

string name = employee.getName();

customer.setName(„mike”);

if (paycheck.isPosted())…

Alegeți câte un nume per concept

Alegeți un cuvânt pentru fiecare concept abstract și folosiți-l, în așa fel încât să asigurați o anumită consistență sistemului vostru.

Exemplu:

fetch

retrieve

get

Adăugați un context semnificativ

Amplasați numele alese în context pentru cititorul vostru, prin includerea lor în structuri de date și funcții bine denumite.

Example :

firstName, lastName;

street, houseNumber;

city, state, zipcode;

Formează o adresă?

You can add context by using prefixes: addrFirstName, addrLastName, addrState, etc.

Mai bine: folosiți o structură de date denumită Address.

Concluzie

Ce este dificil în alegerea de nume bune?

Cel mai greu lucru în alegerea de nume este folosirea abilităților descriptive și a unui context cultural comun (este mai mult o problemă de educație decât una tehnică, de afacere sau de manageriere).

Folosiți unele din regulile descrise și vedeți dacă vă va îmbunătăși lizibilitatea codului vostru. Va da roade în scurt timp și va continua să dea roade și pe termen lung.

Funcții

Prima regulă:

Funcțiile trebuie să fie mici !

A doua regulă:

Funcțiile trebuie să fie mai mici de atât !

Blocuri și indentare

Blocurile din structurile if, else, while, și așa mai departe, trebuie să aibă marimea de maxim o linie.

Dacă sunt mai lungi, introduceți o nouă funcție!

This keeps the enclosing function small and adds documentary value because the function called within the block can have a nicely descriptive name.

Functions should not be large enough to hold nested structures. Therefore, the indent level of a function should not be greater than one or two.

This makes the functions easier to read and understand.

Functions should do one thing ! (single responsibility principle)

How to test if a functions does only one thing :

  1. Should have only one answer for the question : “What does the function do?”
  2. If you can extract another function from it with a name that is not merely a restatement of its implementation

Reading Code from Top to Bottom: The Stepdown Rule

The code should be read like a top-down narrative

Use Descriptive Names

It’s nothing wrong in introducing a long name.

A long descriptive name is better than a short enigmatic name.

A long descriptive name is better than a long descriptive comment.

Use a naming convention that allows multiple words to be easily read in the function names.

Example : calculateEmployeeSalaryBonus

Be consistent in your names ! Use the same phrases, nouns, and verbs in the function names you choose for your modules.

Function Arguments

Based on the number of arguments, the functions can be :

Niladic – zero number of arguments (ideal case)

Monadic

Dyadic

Triadic – should be avoided

Polyadic – shouldn’t be used

Common Monadic Forms

Common reasons to pass a single argument into a function :

You may be asking a question about that argument.

boolean fileExists(“MyFile”)

You may be operating on that argument, transforming it into something else and  returning it.

InputStream fileOpen(“MyFile”)

Other case for monadic forms : events.

In this form there is an input argument but no output argument.

The overall program is meant to interpret the function call as an event and use the argument to alter the state of the system.

void passwordAttemptFailedNtimes(int attempts)

Flag Arguments – avoid using them

Passing a boolean into a function is a truly terrible practice.

It violates the single responsibility principle for the function ( the function does one thing if the flag is true and another if the flag is false).

It immediately complicates the signature of the method.

Dyadic Functions

Dyadic functions are harder to understand than monadic functions.

writeField(name)

writeField(output-Stream, name)

There are cases, of course, where two arguments are appropriate.

Point p = new Point(0,0);

Triads

Functions that take three arguments are significantly harder to understand than dyads.

You got to have a very good reason for introducing a function that takes 3 arguments.

Argument Objects

When a function seems to need more than two or three arguments, it is likely that some of those arguments ought to be wrapped into a data structure of their own.

Circle makeCircle(double x, double y, double radius);

Circle makeCircle(Point center, double radius);

X and Y are part of a concept that deserve a name of its own, so wrapping them in a separate data structure is a good idea.

Conclusion

If you follow the previously presented rules, your functions will be:

  • short
  • well named
  • nicely organized

Never forget that your real goal is to tell the story of the system, and that the functions you write need to fit cleanly together into a clear and precise language to help you with that telling.

Comments

Comments are not “pure good”.  Comments are, at best, a necessary evil.

The proper use of comments is to compensate for our failure to express ourself in code. Comments are always failures.

We must have them because we cannot always figure out how to express ourselves without them, but their use is not a cause for celebration.

So when you find yourself in a position where you need to write a comment, think it through and see whether there isn’t some way to turn the tables and express yourself in code.

Comments Do Not Make Up for Bad Code

One of the more common motivations for writing comments is bad code.

Rather than spend your time writing the comments that explain the mess you’ve made, spend it cleaning that mess.

Explain Yourself in Code

There are certainly times when code makes a poor vehicle for explanation, but most of the time it’s easy to express yourself through code

Which would you rather see? This:

// Check to see if the employee is eligible for full benefits

if ((employee.flags & HOURLY_FLAG) &&

(employee.age > 65))

Or this?

if (employee.isEligibleForFullBenefits())

It takes only a few seconds of thought to explain most of your intent in code. In many cases it’s simply a matter of creating a function that says the same thing as the comment you want to write.

Good Comments

Some comments are necessary or beneficial.

Keep in mind, that the only truly good comment is the comment you found a way not to write.

Bad Comments

Most comments fall into this category.

Usually they are excuses for poor code or justifications for insufficient decisions.

Rule : Don’t Use a Comment When You Can Use a Function or a Variable

  • Position Markers

Sometimes programmers like to mark a particular position in a source file.

// Actions //////////////////////////////////

There are rare times when it makes sense to gather certain functions together beneath a banner like this.

  • Closing Brace Comments

Sometimes programmers will put special comments on closing braces.

If you find yourself wanting to mark your closing braces, try to shorten your functions instead.

  • Commented-Out Code

Few practices are as odious as commenting-out code.

  • Nonlocal Information

If you must write a comment, then make sure it describes the code it appears near.

  • Function Headers

Short functions don’t need much description.

A well-chosen name for a small function that does one thing is usually better than a comment header.

Conclusion

When you feel the need to introduce a new comment, it’s better to  extract a function with a descriptive name or introduce variable

Formatting

Vertical Formatting

How big should a source file be?

Vertical Openness Between Concepts

Nearly all code is read left to right and top to bottom.

Each line represents an expression or a clause, and each group of lines represents a complete thought.

Those thoughts should be separated from each other with blank lines.

Vertical Distance

Concepts that are closely related should be kept vertically close to each other .

Variable Declarations.

Variables should be declared as close to their usage as possible.

Because our functions are very short, local variables should appear a the top of each function.

Dependent Functions.

If one function calls another, they should be vertically close, and the caller should be above the callee, if at all possible.

This gives the program a natural flow.

Indentation

To make the hierarchy of scopes visible, we indent the lines of source code in proportion to their position in the hierarchy.

Statements at the level of the file are not indented at all.

Methods within a class are indented one level to the right of the class.

Implementations of those methods are implemented one level to the right of the method declaration.

Block implementations are implemented one level to the right of their containing block, and so on.

Testing your code

The Three Laws of TDD – test driven development

First Law You may not write code until you have written a failing unit test.

Second Law You may not write more of a unit test than is sufficient to fail, and not compiling is failing.

Third Law You may not write more production code than is sufficient to pass the currently failing test.

Summary

Comments:

C1: Inappropriate Information( ex: author, last modified-date)

C2: Obsolete Comment

C3: Redundant Comment

A comment is redundant if it describes something that adequately describes itself.

i++; // increment I

C4: Poorly Written Comment

If you are going to write a comment, take the time to make sure it is the best comment you can write

C5: Commented-Out Code

Functions

F1: Too Many Arguments

Functions should have a small number of arguments.

F2: Output Arguments

Readers expect arguments to be inputs, not outputs. If your function must change the state of something, have it change the state of the object it is called on.

F3: Flag Arguments

Boolean arguments states that the function does more than one thing.

F4: Dead Function

Methods that are never called should be discarded.

General

G1: Inconsistency

If you do something a certain way, do all similar things in the same way.

G2: Function Names Should Say What They Do

Date newDate = date.add(5);

Would you expect this to add five days to the date? Or is it weeks, or hours?

Better use : addDaysTo or increaseByDays.

G3: Replace Magic Numbers with Named Constants

The number 86,400 should be hidden behind the constant SECONDS_PER_DAY.

G4: Encapsulate Conditionals

Boolean logic is hard enough to understand without having to see it in the context of an if

or while statement.

Extract functions that explain the intent of the conditional.

Example:

if (shouldBeDeleted(timer))

is preferable to

if (timer.hasExpired() && !timer.isRecurrent())

G5: Functions Should Do One Thing

It is often tempting to create functions that have multiple sections that perform a series of operations.

Functions of this kind do more than one thing, and should be converted into many smaller functions, each of which does one thing.

Names

N1: Choose Descriptive Names

This is not just a “feel-good” recommendation. Names in software are 90 percent of

what make software readable. You need to take the time to choose them wisely and keep

them relevant.

N2: Use Long Names for Long Scopes

The length of a name should be related to the length of the scope. You can use very short

variable names for tiny scopes, but for big scopes you should use longer names.

N3: Avoid Encodings

Names should not be encoded with type or scope information

Bibliography

The Clean Coder: A Code of Conduct for Professional Programmers (Robert C. Martin Series)

Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin Series)