Come scrivere programmi incomprensibili |
| A cura di Davide Bianchi |
Capita spesso di dover mettere le mani nel codice scritto da
altri e di domandarsi "ma l'ha fatto apposta ???" perche' lo
stesso e' ASSOLUTAMENTE INCOMPRENSIBILE.
|
|
Doverosa premessa
|
Questo documento non e' fatto per insegnarvi come scrivere del
codice, semmai come NON scriverlo. Mi e' venuta voglia di scriverlo tanto tempo fa'... quando mi capitava in mano il codice scritto da qualcun altro che sembrava essere fatto per vincere un campionato di "obfuscated code", poi non ci ho piu' pensato per un po'. Mi e' ritornata la voglia qualche giorno fa' quando ho ricevuto uno spezzone di codice da controllare e mi sono ritrovato a dover riscrivere e verificare ogni riga per cercare di capire che accidente fa' sta' cosa... Ecco quindi il mio contributo all'argomento. Per cortesia, non seguite i miei consigli... se non altro non dite che ve li ho dati io.
|
|
|
Principi generali
|
Per raggirare un programmatore che deve mantenere o modificare il
vostro codice, dovete sapere come pensa. Ok, lui ha il vostro
gigantesco programma, non ha il tempo per leggerlo completamente,
figuriamoci per capirlo. Quello che vuole, e' trovare rapidamente il
punto dove fare i suoi cambiamenti, farli alla svelta, andarsene a
casa e non avere nessun effetto collaterale per questo.
Lui vede il vostro codice come attraverso un tubo, puo' vedere solo una piccola parte del codice, quello di cui volete essere sicuri e' che non arrivi MAI a capire il disegno generale, e che sia il piu' difficile possibile per lui trovare il punto in cui apportare i suoi cambiamenti. Ma ancora piu' importante e' che sia incredibilmente difficile per lui modificare qualsiasi cosa senza avere disastrosi effetti su tutto il resto del codice. Per fare cio' dovete rendervi conto che ogni particolare aspetto di un linguaggio (se inpropriamente utilizzato) rende il codice incomprensibile.
|
|
|
Nomenclatura
|
La parte migliore nello scrivere codice assolutamente incomprensibile
e' data dalla capacita' (quasi un'arte) di trovare nomi assurdi per
variabili, funzioni e metodi. Questi nomi non significano nulla per il
computer, questo vi da' l'opportunita di usare tutte le tecniche
possibili per instupidire il programmatore.
1. Nuovi usi per il "libro dei nomi" (ISBN: 0-380-78047-X)
2. Errori grammaticali
3. Siate astratti
4. A.C.R.O.N.I.M.I.
5. Sinonimi e contrari Non soccombete MAI alla richiesta di documentare il vostro codice e di scrivere un glossario.
6. Usate lingue straniere
7. maIusColE
8. Riciclate i nomi
9. Lettere accentate typedef struct { int i; } ìnt; Se non ve ne siete accorti, la 'i' dell'ultimo 'int' e' una lettera accentata, questa pero' passa facilmente inosservata, cosi' vi ritrovate una cosa che sembra una variabile mentre e' una struttura.
10. Usate a vostro vantaggio la massima lunghezza delle variabili
Per esempio var_unit_dimension e var_unit_expression, queste due sembrano diverse, ma per il compilatore sono una sola... devo dire altro ?
11. Sottolineato, un'amico.
12. Mescolanza di lingue
13. Il codice ASCII e' composto da 255 caratteri
14. Usare nomi comuni per le variabili marypoppins = (julieAndrews + starship) / mailbox; Questo confonde il lettore che ha difficolta' a dissociare la parola dal suo contesto "solito".
15. Variabili tipiche
16. Ignorare le convenzioni implicite
17. La 'l' (elle) somiglia molto ad '1' (uno)
18. Essere creativi con i parametri delle funzioni
19. Utlzo dle abbrvzni (utilizzo delle abbreviazioni)
20. Sottostimare le funzioni isValid( x ) Dovrebbe verificare la validita' del suo parametro, ma se contemporaneamente lo converte in binario e lo memorizza in un database...
21. Notazione ungherese Per chi non lo sapesse, la Notazione Ungherese e' la pratica di anteporre al "nome" vero della variabile un "prefisso" che identifica il tipo e lo scopo della variabile. Cosi' abbiamo per esempio intValore, strTesto, objOggetto e cosi' via. Il trucco sta' nell'utilizzare prefissi identici ma che abbiano diversi significati in vari punti del programma, cosi' abbiamo:
intXXX = Intervallo Non Trovato... e cosi' via.
22. Notazione Ungherese 2
22. Ridurre, riusare, riciclare
23. Usare nomi che somigliano molto ad istruzioni
24. Usare nomi che non c'entrano niente con quello che appare a
video
|
|
|
Cammuffamento
|
Un'altra grande abilita' per scrivere codice incomprensibile e'
l'arte del cammuffamento, ovvero nascondere certe cose e far si'
che altre cose appaiano cio' che non sono. Molto di questo dipende
dal tipo di linguaggi che state usando e dagli strumenti che il
"manutentore" si trovera' in mano.
1. Mascheramento del codice come commento e vice versa
for(j=0; j<array_len; j+ =8)
{
total += array[j+0 ];
total += array[j+1 ];
total += array[j+2 ]; /* Main body of
total += array[j+3]; * loop is unrolled
total += array[j+4]; * for greater speed.
total += array[j+5]; */
total += array[j+6 ];
total += array[j+7 ];
}
Senza la colorazione della sintassi avreste notato che tre linee di
codice sono commentate ?
2. Spazi, indentazione e linee bianche
for(kappa=0;kappa<=_kappa;kappa++) { if(llama+kappa<0)
bzap();
if(llama+kappa>3){effe+=-l
+3;
data();
}
}
3. Istruzioni che proseguono nelle linee successiveAnche questo dipende molto dal compilatore che state usando, con molti compilatori e' possibile interrompere una linea e proseguirla sulla linea successiva aggiungendo un "\" nel punto di interruzione. In questo modo e' possibile avere una cosa del tipo:
int data\ base=0;Lo scopo e' rendere impossibile una ricerca rapida della variabile "database".
4. Nascondere definizioni e dichiarazioni in mezzo ai commenti
5. Nascondere variabili globali Il programmatore passera' giorni cercando nel vostro codice una classe con quel nome...
6. Usate dei sinonimi #define per assegnare diversi "sinonimi" alla stessa funzione/variabile, il programmatore ricevera' pochi riferimenti, quindi avra' molte piu' probabilita' di ritrovarsi con un disastro quando apportera' le sue modifiche. Questo e' particolarmente effettivo se unito alle (3) e (4).
7. Sovraccarico
8. Usare le funzionalita' del linguaggio
if (a==b) {
c='pippo';
else
c='pluto';
}
Usate le funzionalita' del linguaggio piu' complicato che esiste:
c=select decode(sign(a-b),0,"pippo","pluto");
9. "Piccoli" errori di digitazione...
#define TRUE 1 /* ...some comment */*0 #define FALSE 0 /* ...some other comments */Si vede l'errore?
|
|
| Documentazione |
Dato che il computer ignora i commenti e la documentazione, potete
mentire a vostro piacimento e fare tutto cio' che e' in vostro
potere per ridurre alla disperazione il povero programmatore.
1. Mentire nei commenti.
2. Documentare l'ovvio, ignorare lo strano
3. Uso dei tools di documentazione
4. Uso della documentazione di progetto. Per ogni "passo" dell'alogoritmo scrivete un paragrafo che ne spieghi tutti i dettagli, usate un formato di capitolo/paragrafo/sottoparagrafo/sotto-sottoparagrafo e cosi' via. Come minimo devono essere 5 livelli. In maniera tale che si abbia alla fine una cosa come 1.2.4.6.2.13 - Visualizza il risultato dell'attivita'.... Quando scrivete il codice effettivamente, usate delle funzioni di nome Act1_2_4_6_2_13() senza commentarle. Dopotutto e' per questo che e' stata scritta la documentazione no ? Dato che la documentazione e' auto-numerata, lunga e complessa, e' estremamente difficile mantenerla in sincronia con il codice vero. Ma questo non e' un problema vostro. In effetti voi non farete assolutamente niente per mantenere la documentazione aggiornata, anzi il contrario.
5. Unita' di misura.
6. Bugs
7. Commenti alla Monty Python
|
|
|
Progettazione
|
La chiave per scrivere un codice facilmente comprensibile e
mantenibile e' quella di definire e specificare ogni cosa in un
solo posto. Se cambiate idea o volte fare delle modifiche,
dovete farlo in un solo posto. Pertanto, la chiave per scrivere
codice ASSOLUTAMENTE INCOMPRENSIBILE e' quella di ripetere e
sparpagliare le cose in una marea di posti diversi.
Questo funziona particolarmente bene con certi linguaggi, meno con altri. Per esempio, in Java/C/C++. Per esempio e' praticamente impossibile cambiare il tipo di una variabile perche' i vari cast/controlli di tipo falliscono miseramente. Mai verificare il tipo/valore dei parametri di input. Questo dimostrera' assoluta fiducia nelle scelte/intelligenza dell'utente. E si trasformera' in un incubo per il povero programmatore che avendo cambiato un int in un long otterra' un GPF o un crash del sistema senza alcuna traccia di dove andare a guardare. Per la stessa ragione mai usare Assert(). Una o due Assert() messe bene possono trasformare una settimana di lento e penoso debug in una festa di 5 minuti. Usare allegramente il copia/incolla per riprodurre parti di codice ovunque sia necessario. Questo e' anche utile se il vostro lavoro e' misurato in linee di codice scritte... Usate variabili globali ed array statici per memorizzare informazioni per tutto il programma, in questo modo non si avra' mai una chiara comprensione di COSA contengono tali variabili. Disseminate il programma di funzioni inutili e mai richiamate. Ignorate categoricamente i valori di ritorno di altre funzioni. Se state usando un linguaggio OOP gettatevi a pesce nell'ereditarieta', definite una classe base e poi derivatela fino al 20o livello aggiungendo sempre cose assolutamente inutili e ridefinendo sempre le stesse funzioni. In questo modo non si sapra' mai se state richiamando un metodo nella classe madre o in quella attuale. Sempre se state usando un linguaggio OOP, mescolate sempre variabili pubbliche con metodi pubblici. Scrivete inutili funzioni-ponte e "wrapper" per richiamare codice non scritto da voi, ed anche scritto da voi. Assicuratevi di avere un quarto livello di redirezione: la funzione A() che chiama B() che chiama C() che chiama D() che fa' il lavoro. Moltiplicate le funzioni usate e non centralizzate, invece di scrivere una sola funzione setAlignment() che accetti un parametro per indicare l'allineamento, scrivete 4 funzioni diverse setLeft, setRight etc. Abbiate cura di copiare il codice base e la logica, questo per rendere piu' difficile il mantenerle in sincronia. In Java, ogni volta che un metodo riceve un oggetto come parametro (cioe' quasi sempre), quell'oggetto e' modificabile. Usare estensivamente questo effetto collaterale per modificare lo stato interno del parametro.
|
|
|
Lasciate i vostri bug
|
Le Exceptions non servono. Il codice scritto bene non fallisce mai,
quindi non perdete tempo a gestire Exceptions.
Lasciare qualche bug nel vostro programma fornira' al manutentore che verra' dopo di voi qualche cosa di divertente da fare. Un bug piazzato bene lasciera' al programmatore l'eccitazione di scoprire DOVE e QUANDO e' stato introdotto. Il sistema piu' semplice per ottenre quest'effetto e' semplicemente di non testare mai il vostro codice. Un altro ottimo sistema e' lasciare del codice che viene eseguito solo in fase di debug (usando #ifdef), questo lasciera' al programmatore diversi problemi che appaiono quando il programma e' in fase di debug ma spariscono quando il programma e' in esecuzione normale. Mai ricompilare. Compilate il codice una volta per ottenere un eseguibile, se funziona fate alcune modifiche al codice e non ricompilatelo piu'. Fate continui miglioramenti al vostro codice (dopotutto lo fa' anche la Microsoft no ?). Includete nel vostro codice potentissime librerie aggiuntive e non usatele mai. Accertatevi di aver lasciato qualche Warning di compilazione nel vostro codice. Usate poi l'opportuno parametro del compilatore per sopprimerne la segnalazione. In questo modo il programmatore che tentera' di ricompilare il codice si trovera' con una serie di segnalazioni anomale e perdera' giorni nel tentativo di capire cosa e' successo. Se scoprite un bug nel vostro compilatore/interprete, accertatevi di sfruttarlo al massimo, anzi, fate in modo che tale bug divenga essenziale per il vostro codice per poter funzionare correttamente. Se il vostro compilatore si lamenta di "variabili inutilizzate", non eliminatele, trovate un modo furbo di usarle, per esempio i=i;
|
|
|
Varie...
|
Usate array multidimensionali. E tanti.
Usate particolari posizioni all'interno di lunghi array come flag. Non documentate mai ovviamente l'utilizzo. Mai usare costanti "normali", se vi serve una costante che vale 'A', usate 65 o 0x41 o 0101 per indicarla. Usare l'esadecimale o l'ottale ogni volta che e' possibile. Fate libero uso del fatto che 0x10, 010 e 10 non sono la stessa cosa... Passate tutti i dati come void* e poi effettuate un cast per ottenere il giusto tipo, anche usare il byte offset invece che un cast esplicito e' utile. Usate estensivamente switch innestati. E' il sistema piu' complicato da identificare. Mai usare costanti mnemoniche, usate sempre il valore numerico puro. Cercate di "impacchettare" il maggior numero di istruzioni su una singola linea (chi si ricorda lo "spaghetti code" ?) Mantenete tutto il codice inutile nel vostro programma, anche se non viene mai richiamato. Se necessario aggiungete funzioni inutili prendendole da altri programmi, anche se non c'entrano niente con il programma attuale. Fate le vostre funzioni le piu' grosse possibili, 1000 o piu' linee di codice non sono poche. Lasciate almeno un paio di file che non si trovano durante la compilazione. Il sistema piu' semplice e' usare dei riferimenti assoluti al vostro disco locale. Per esempio #include "D:\\myfile.h" Almeno una variabile dovrebbe essere scritta dappertutto, ma mai letta (e quindi usata).
|
|
|
Da Parte di Paolo Antonelli
|
Una particolare feature del C e quindi del C++ (e anche del C# non
gestito, se non ricordo male) e' quella dell'aritmetica dei puntatori.
Sicuramente non c'e' bisogno che vada avanti nella spiegazione, per cui
passo direttamente agli esempi:
int main() {
int vettore[100];
void * puntatore = vettore;
// Questo lo metto qui.....
vettore[20] = 50;
// inizializzazione
vettore[5] = 10;
int elemento = vettore[5];
int elemento2 = (int)((char*)(puntatore))[5 * sizeof(int)];
// e se 5 e' cablato.....
int elemento3 = (int)((char*)(puntatore))[20];
// ovviamente ti sarai anche accorto che qui sopra c'e' anche un altro
// effetto malefico e' cioe' che prendendo un singolo elemento quando il
// vettore e' un char * funziona solo con valori minori di 256, per cui:
vettore[5] = 266;
elemento = vettore[5];
elemento2 = (int)((char*)(puntatore))[5 * sizeof(int)];
elemento3 = (int)((char*)(puntatore))[20];
// A questo punto solo in elemento c'e' 266, mentre in elemento2 e elemento3
// c'e' ancora 10...bello no?
// mentre che ne pensi dell'istruzione seguente? potrebbe sembrare un
// bug, ma forse e' stato sbagliato giusto?!?!?!
int elemento4 = vettore[5 * sizeof(int)];
return 0;
// ah! dimenticavo, ovviamente va bene solo su compilatore per
// cui sizeof(int) e' 4 byte: "colpa del compilatore, il codice
// quando l'ho fatto era giusto"
}
Spero che ti piaccia, soprattutto l'ultima (elemento4) e' esagerata,
perche' sembra proprio un errore logico concettuale, a meno che
sia fatto a bella posta.
|
|
|
Conclusioni
|
Non penso di avere esaurito lo scibile di cio' che e' inumano
mettere in un programma ma e' normale trovare, se volete aggiungere
le vostre impressioni sulla cosa, siate liberi di scrivermi.
|
|
Comments Max length of comments: 1000 chars. |
3 commenti Aggiungo ? dice il 17/04/2008 21:04: L'ho letta in un post di www.Punto-informatico.it , purtroppo non credo che saprei ritrovarlo pero' te lo racconto: si parlava di mala programmazione relativa ai programmi java usati nei comuni italiani. La scrivo con il tono che usi nel sito (piu' o meno) Usa i nomi accentati anche per i file, in modo da rendere impossibile sia il cross platform, che la semplice ricompilazione su un sistema operativo con utf diverso. Complimentissimi per questa pagina, e' divertentissima. Ora esplorero' il resto del sito. Gray Alessandro dice il 30/04/2008 21:13: Assolutamente fantastico! Vedrò di mettere in pratica i tuoi consigli ;) Francesco Paolini dice il 22/09/2008 18:43: nei db in cui un campo può essere lungo poche lettere, devi ridurre il nome del campo. utilizza le prime 2 lettere per ricordare da che tabella viene. Quindi se hai una tabella di nome sche2, da cui prelevi il peso netto e il peso lordo, inventa S2PENE e S2PELO. il tizio dopo di te non riuscirà a disassociare il campo dal contesto normale Add a comment (max 1000 chars)
|
| L'Autore |
Davide Bianchi,
works as Unix/Linux administrator for a "network security" company of Haarlem. Contacts: mail: davide AT onlyforfun.net , ICQ: 268751033, Jabber: davideyeahsure AT gmail.com Skype: davideyahsure |
| Contribuire | Volete contribuire? Leggete come! |
| Copyright | This site is made by me with blood, sweat and gunpowder, if you want to republish or redistribute any part of it, please drop me (or the author of the article if is not me) a mail. |
This site isn't optimized for vision with any specific browser, nor
it requires special fonts or resolution.
You're free to see it as you wish.
Ultimo aggiornamento: 30 Settembre 2004