Scrivere codice leggibile
di:Andrea Raimondi
La implementazione di moduli sorgenti, più o meno complessi, dovrebbe fare uso di una tecnica che io chiamo "Self Explaining Code", un codice cioè che si spiega da solo senza porre commenti che pure agevolerebbero la scrittura di codice efficiente e con minor numero di errori. La tecnica che qui presentiamo, se applicata correttamente, consente di ridurre notevolmente i costi, in termini di tempo, sia di manutenzione del codice, che di correzione e, talvolta, prevenzione di errori. Prendiamo, a titolo esemplificativo, questo pezzo di codice :
#include
void PrnN( int N );
void Tab( int N1,int N2 );
void Tab2( int N );
A voi sembra chiaro ciò che è scritto ? A me, francamente, no. Se
può non essere un problema su programmi piccoli( trecento righe ),
lo è sicuramente su programmi grandi che raggiungono le mille e più righe.
Adesso provate a capire qual'e' lo scopo di queste tre
funzioni : riuscite a capire che fanno ? Vi do una mano, le implemento :
void PrnN( int N )
{
  printf( "%d", N );
}
void Tab( int N1,int N2 )
{
  int I;
  for( I = 0; I < N2;I++ )
  {
   PrnN( N1*I );
  }
}
void Tab2( int N )
{
  Tab( 2,N );
}
Adesso avete capito a che servono ? Beh, adesso dovreste esserci arrivati.
Ora, provate ad immaginare che prima di Tab e Tab2 ci siano altre,
diciamo, trecento righe di codice. Lo stesso programmatore che, anche solo
un mese prima, ha scritto questo codice, troverà non poche difficoltà, ad esempio, a voler mettere un new-line( carattere "\n" )
nella funzione PrnN, semplicemente perché, nel migliore dei casi, non ricorderà dove essa si
trova. Naturalmente per procedure così piccole non vi sono reali problemi,
ma pensate se, ad esempio, c'è un errore nell'algoritmo di ordinamento...
le cose si fanno più complesse, in quel caso. E se magari gli algoritmi di ordinamento sono più di uno e differiscono di poco,
quindi avranno nomi
simili, con il metodo sopra descritto, la manutenzione del codice, ma anche la sola modifica di una riga, comporta perdite di tempo davvero notevoli. Ma vediamo adesso i prototipi e le implementazioni sopra riportate, modificate con la tecnica del "Self - Explaining - Code" :
void PrintNumero( int Numero );
void Tabellina( int Numero ,int FinoA );
void Tabellina_Del_2( int FinoA );
Ecco adesso è più chiaro, no ? Ora mantenere questo codice non è una
impresa titanica, ma vediamo le implementazioni :
void PrintNumero( int Numero )
{
  printf( "%d", Numero );
}
void Tabellina( int Numero ,int FinoA )
{
  int Conta;
  for( Conta = 0; Conta < FinoA;Conta++ )
  {
   PrintNumero( N1*I );
  }
}
void Tabellina_Del_2( int FinoA )
{
Tabellina( 2,FinoA );
}
Ah ! Ora si che si capisce ! Adesso se le righe di codice che si trovassero, eventualmente, tra le procedure e funzioni elencate ed implementate fossero tre o quattrocento, essendo procedure e funzioni mnemoniche, che possiamo cioè ricordare a memoria, sarà più facile sia utilizzarle che mantenerle. Tornando all'esempio degli algoritmi di ordinamento, ogni procedura sarà diversa nel nome a causa delle diverse quantità da ordinare. E' o no un passo avanti nella scrittura del codice ?
Facciamo un esempio reale. In Delphi ogni form è indicata nella forma
"FormN" dove N è un numero progressivo maggiore di 0. Se, ad esempio,
creassimo cinque forms diverse e con diversi scopi e non cambiassimo il
nome di default, quando la form principale richiama una qualsiasi delle form secondarie, dovreste ogni volta andare a vedere che operazioni svolge quella determinata Form. Ma se sono cinque la cosa non è gravosa, il problema comincia ad essere sentito quando sono cinquata ! E cinquanta forms in un progetto medio - grande ci stanno sicuramente !
Adesso lasciamo perdere le forms, dedichiamoci ai controlli : se lasciamo i nomi di default, provate ad immaginare che succederebbe con sei bottoni su una stessa form. Prendiamo, a titolo esemplificativo, una semplice calcolatrice in
Delphi :
Ecco uno squarcio della prima versione( solo nomi di default ) :
procedure TForm1.Button15Click(Sender: TObject);
var R1,R2 : LongInt;
  T1,
  T2 : LongInt;
  ErrCode : Integer;
  T : String;
  begin
   N2 := Edit1.Text;
   val( N1,T1,ErrCode );
   val( N2,T2,ErrCode );
   if A then
    R1 := T1+T2
   else if S then
    R1 := T1-T2
   else if M then
    R1 := T1*T2
   else if D then
   begin
    R1 := T1 div T2;
    R2 := T1 mod T2;
   end;
   str( R1,T );
   Edit1.Text := T;
   if D then
   begin
    str( R2,T );
    Edit1.Text := Edit1.Text+'.'+T;
   end;
end;
end.
Ora, questa è una calcolatrice molto semplice, però è emblematica dei problemi che una cattiva scrittura di codice può portare.
Ora vediamone uno dalla versione scritta come si deve( si tratta del corrispondente ) :
procedure TCalcForm.DaiRisultatoClick(Sender: TObject);
begin
SecondoValInserito := ( ValoreCorrente.Text <> '' );
  if Not SecondoValInserito then
   AvvisaUtente( 'Non hai definito il secondo '+
     'valore NECESSARIO per effettuare un qualsiasi calcolo.' )
  else begin
    Operando2 := Da_Stringa_A_Numero( ValoreCorrente.Text );
    case Operazione of
     Addizione : begin
      Risultato1 := Operando1+Operando2;
      ValoreCorrente.Text := Da_Numero_A_Stringa( Risultato1 );
    end;
    Sottrazione : begin
     Risultato1 := Operando1-Operando2;
     ValoreCorrente.Text := Da_Numero_A_Stringa( Risultato1 );
    end;
    Moltiplicazione : begin
     Risultato1 := Operando1*Operando2;
     ValoreCorrente.Text := Da_Numero_A_Stringa( Risultato1 );
    end;
    Divisione : begin
     Risultato1 := Operando1 div Operando2;
     Risultato2 := Operando1 mod Operando2;
     AssegnaValoreALineaInput( True,True,Risultato1 );
     AssegnaValoreALineaInput( True,True,Risultato2 );
    end;
   end;
  end;
end;
end.