Scrivere codice con stile

Ovvero, alcune semplici regole per rendere il vostro codice più leggibile agli altri, inclusi voi stessi domani mattina.

Scrivere del codice è un po' come scrivere un romanzo, anche se l'audience non è particolarmente portata al trasporto emozionale (direi quindi di lasciare perdere lo stream of consciousness come tecnica per scrivere codice).

Come Raymond Queneau rese evidente nel suo libro Esercizi di stile, nella scrittura lo stile è importantissimo e fa la differenza fra un libro che comunica e uno che non comunica. Nello stesso modo, seguire uno stile sobrio ed elegante nella scrittura del proprio codice fa la differenza fra il codice che il vostro collega può prendere in mano e il codice che vi toccherà mantenere a vita: ammesso che fra tre mesi abbiate una vaga idea di ciò che fa.

Armato di tali buoni propositi, ho stilato un breve decalogo di regole che, a mio parere, rendono il codice leggibile e facile da mantenere.

Perchè non la PEP-8?

Già, nel mondo Python esiste già una styleguide, precisamente la PEP-8. Quindi perchè non adottare quella?

Beh, la risposta è che, sebbene molto ben fatta, risulta lievemente eccessiva a tratti: il mio breve decalogo è in realtà un sottoinseme della PEP-8. Se volete seguire la PEP-8, avete la mia stima e la mia ammirazione. Ed anche se non avete intenzione di seguirla, vale comunque la pena leggerla.

Ma ordunque cominciamo con questo set di brevi principi in ordine sparso ed eventuale:

Pyflakes

Non ci sono scuse: il vostro codice deve passare la validazione di pyflakes. pyflakes è un simpatico programmino che legge il vostro codice e controlla se ci sono errori (ad esempio typo) o ambiguità nello stesso.

Sebbene il suo scopo non sia controllare lo stile della programmazione, vi forza a non usare scorciatoie di difficile interpretazione da parte di un umano.

Per integrarlo con il vostro TextMate, seguite questa guida.

In alternativa, potete utilizzare anche pylint, che effettua un'analisi statica del codice decisamente superiore a pyflakes. Il suo utilizzo però non è così intuitivo come pyflakes e rompe un pochettino di più le balle (motivo per cui spesso il mio codice contiene commenti condizionali che disabilitano alcuni particolari controlli di pylint: perché pylint ha ragione, ma spesso non si può fare altrimenti).

Dite no all'import *

Niente from spam import *. Nein nein nein. Es ist verboten.

Usa gli spazi, Luke

Non so se Yoda nei suoi insegnamenti faccia riferimento all'importanza dello spazio bianco e della newline. Nel caso quel simpatico vecchietto verde se ne sia dimenticato, lo rammento io.

Indentazione a 4 spazi. No tab. No no no no no NO! Mama mia mama mia mama mia use the space! Guido Van Rossum has a devil set aside for me, for meeeee...

Usate due righe vuote (non piene di spazi bianchi, vuote) fra classi e definizioni top-level, e una fra metodi della stessa classe.

Deve sempre essere lasciato uno spazio dopo una virgola, ma mai prima (è un disonore per il programmatore soffrire di spaziazione precoce).

Deve sempre essere lasciato uno spazio prima e dopo gli operatori (+, -, *, =, ==, etc.). Fate respirare le condizioni e gli assegnamenti!

Ogni linea non deve eccedere gli 80 caratteri, inclusi gli spazi davanti. I sistemisti incominceranno a sorridervi, ogni tanto.

Le docstring a tre deve avere le ultime tre virgolette su una riga a se stante.

Non seguire queste regole porta al lato oscuro della programmazione (dove Visual Studio è l'unico che ci capisce qualcosa del vostro codice).

Esempio:

import spam


def ask(food):
    return "We have spam, %s and spam" % food


class Viking(object):
    """Just a viking
    """

    def __init__(self, name, horns = False):
        self.name = name
        self.horns = horns 

    def say(self):
        return "Spam spam spam beautiful spam"

I commenti servono per commentare

E sono come il tizio seduto di fianco al telecronista nelle partite. Se parla troppo e a sproposito rompe solo le balle.

Quindi:

NON USATE COMMENTI PER SPEZZARE PARTI LOGICHE DEL PROGRAMMA. SPLITTATE I FILES.

Evitate di scrivere commenti del genere:

############################################################
### Ciao mamma guarda come mi diverto con l'hash (-ish?) ###
############################################################

E non mi riferisco al testo ma all'uso eccessivo dell'hash e delle ascii art.

Questo è un commento:

# BBB: You are not supposed to understand what happens in the next 70
# lines. I myself have only a faint idea.

Questo è eccessivo, brutto, e non dice granchè:

####################
####################
#### OMGBADCODE ####
####################
####################

Commentate estensivamente laddove serve: commenti di 7, 8 righe o più non sono un problema, a patto che il commento abbia un senso compiuto, si riesca a leggere scorrevolmente, e dettaglia esplicitamente un'algoritmo.

Alleggeriamo la lettura separando blocchi logici di codice con una linea vuota di tanto in tanto.

Esempio:

# Here we first check whether the request is valid and contains all
# the data. If this is not the case, we bail out immediately with an
# error; otherwise we continue iteration on each item passed,
# sending the payload to the queue server.
qs = self.get_qs_conn()

if not self.validate_req(request):
    raise InvalidRequest()

for item in request.items:
     payload = IPayload(item)
     qs.send(_channel, payload)

Invece, questo è un obbrobrio

# get conn
qs = self.get_qs_conn()

if not self.validate_req(request):
    # bailing out here! no good!
    raise InvalidRequest()
# for each item
for item in request.items:
    payload = IPayload(item)
    # send to qs
    qs.send(_channel, payload)

La lingua Inglese è preferita all'Italiano. Non per altro, ma l'Italiano tecnico tende a diventare un Inglish, con termini quali forkare, splittare, processatori di coda e pitoni.

Direi di evitare di dare a Dante tale dispiacere.

All'inizio del bootrap dell'applicazione

Incappai in un bug nell'__init__ e ci fu una strana situazione. Evitiamo di discendere all'Inferno, con Emacs come unica guida: non mettiamo logica nell'__init__ se non strettamente necessario.

Import(ante)

Se non ci sono particolari motivi funzionali o prestazionali, tutti gli import devono stare in cima al file, davanti al codice applicativo (quindi, ante), seguendo questo ordine:

  1. stdlib imports
  2. third-party imports
  3. same project, different library imports
  4. Gli import assoluti davanti ai from x import y.

Esempio:

import os, logging
from xml.dom.minidom import parse_string

from django.db import models
from django.utils.translation import ugettext as _

from my.project.utils import rot13

import base

Docstrings

Le docstring sono delle specie di commenti++, quindi come regola d'oro andrebbero messe. Valgono però un paio di regole, alcune di stile e altre di utilizzo. Partiamo dalle regole di utilizzo:

  1. No alle docstring vuote. Sono una presa per il culo. O non le mettete o le riempite. E non mi importa se è lo snippet di TextMate che ce le mette: o le mettete o le levate.
  2. No alle doctsring sbagliate (spesso rimasugli di copia/incolla).
  3. No alle docstring inutili, ovvero quelle che non aggiungono nulla a ciò che già so.

Le regole di stile sono altrettanto semplici:

  1. Le docstring cominciano con tre doppi apostrofi (""") e finiscono con tre doppi apostrofi su di una linea che contiene solo quelli.
  2. La docstring è separata dal codice da una linea vuota.
  3. La docstring è indentata allo stesso livello del codice che la segue.
  4. Qualora la docstring sia molto lunga (ad esempio per classi e metodi) deve essere così formattata: avere una prima riga descrittiva che non superi gli 80 caratteri, poi una riga vuota e poi un testo che spiega nel dettaglio i parametri del metodo, il funzionamento, le return value etc.

Quindi, ecco una breve galleria degli orrori delle docstring:

def view(request):
    """View contact info."""

def store_locator(request, **kwargs):
    """
    store locator view
    """

class GeoMagDatabase(Database):
    """
    """

class RegistrationListView(BrowserView):
    """
    Applications List for JobPost browser view
    """

Il rendere queste docstring decenti viene lasciato come sercizio al lettore.

Dirty deeds (done here cheap)

Siccome il 90% dei lettori di questo post ha ricevuto un'educazione cattolica o paracattolica (leggasi: è andato a catechismo) sarà familiare con il concetto di peccato e senso di colpa, e sopratutto con il concetto che i peccati si commettono molto più spesso di quanto si voglia ammettere, e poi si hanno i sensi di colpa.

Questo succede anche quando si scrive del codice e la soluzione è sempre la stessa: una piena confessione.

Se, per qualunque motivo, avete fatto un qualcosa di non molto pulito e sapete che ciò non è bene (senso di colpa), vi si prega di avere la decenza di segnalarlo tramite un apposito commento, utilizzando uno degli identificatori qui sotto riportati:

BBB (Bad Bad Bad): significa che il codice in questione è una porcata o un hack orrendo.

TODO: significa che il codice è incompleto, non funziona in alcuni casi oppure le sue performance sono ridicole e quindi va sistemato.

FIXME: alternativa a TODO.

I commenti BBB e TODO vanno sempre scritti appendendo all'identificatore una breve spiegazione del perché il codice è così marchiato, ad esempio:

# BBB: if you get this to raise an attribute error and it seems to be
# masking a property, well, this si exactly the case, as __getattr__
# will swallow any error on property getters

# BBB: This is monkey patched in the tests. So if the tests fail here,
# keep in mind this is not the code that is actually ran

# TODO: maybe check if the store is present and switch type

# TODO: remove and make them use the real class methods

Live up to your name

I nomi sono importanti, e darli è un momento di grande responsabilità. Per esempio, quando il Signor Vacca decise di chiamare sua figlia Veneranda fece un grande errore: evitiamo di seguire l'esempio dell'esimio Sig. Vacca anche nel codice che scriviamo.

Le regole del naming dovrebbero essere le seguenti:

  1. Evitare nomi manzoniani: no a isinstance(ramo_del_lago_di_como_che_volge_a_oriente_fra_due_catene_ininterrotte_di_monti, TuttoSeniEGolfi).
  2. È importante nominare bene le classi, le funzioni, i metodi e le proprietà: le variabili interne alle funzioni sono meno importanti, e per ultimo quelle interne alle list comprehension.
  3. È importante che il nome sia intuitivo.
  4. È importante che il nome sia il più corto possibile: non fate IMyPackageDispatcher, tanto quella roba già vive in my.package.interfaces, quindi non clasha per nulla e francamente uno mi fa il piacere di leggersi gli import.

Per quanto riguarda lo stile, ne esistono diversi: qui viene fornita una simpatica tabella per orientarsi:

TipologiaPure PythonZope Python
variabili e parametri underscore_case underscore_case
funzioni underscore_case underscore_case
metodi underscore_case methodCamelCase
attributi underscore_case methodCamelCase
classi CamelCase CamelCase
costanti ALL_CAPS ALL_CAPS
factories underscore_case CamelCase

Quindi, ad esempio avremo:

class IMessage(Interface):
    """A message between nodes
    """

    recipients = Attribute("List of intended recipient")
    content = Attribute("The content of the message")

class IDispatch(Interface):
    """A message dispatcher
    """

    def addNode(node):
        """Adds a node "node" to the network topology
        """

    def dispatch(message):
        """Dispatch "message" to the intended nodes
        """

Siccome nel 90% dei casi il naming non è sufficiente (vedi esempio, addNode non vuol dire granché da solo), ricorrete alle seguenti per esplicare meglio che cosa diavolo fa un determinato metodo/etc:

  1. Docstring nelle interfacce
  2. Docstring nel codice (omettetele se sono già presenti nelle interfacce, se no duplicate e non mantenete le cose allineate!)
  3. Commenti

Buon lavoro!

E con questo è tutto! La tizia con l'ombrello a pois vi saluta, e se fate i bravi programmatori...

Share this on

Share |

On same topics

Commenti

comments powered by Disqus