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.