In questo post, darò una serie ( limitata per altro ) di indicazioni sullo sviluppo del codice che se applicate, lo renderanno impossibile da gestire da qualsiasi persona. Nella loro pedissequa applicazione il codice manipolato diventerà ben presto inutilizzabile anche allo stesso autore.
Diciamo subito che è un post, dai tratti spiccatamente goliardici, ed è ripreso da una pagina web ( link ) scritta in modo magistrale da un esperto softwarista d’oltreoceano. L’intento esplicito dell’autore, rivolto ai soli softwareisiti, ( java per altro ) è quello di contrastare il più possibile l’eventuale scelta aziendale di silurarti. Ne riprenderò le struttura con qualche personale aggiunta e modifica, in modo da integrarli in alcune parti a mio avviso mancanti. Per essere dei veri cani nella programmazione ci vuole metodo e oggi impareremo come.
In verità però c’è da dire anche che, se i presupposti del post sono delle poco etiche rivalse prototerrostiche aziendali, questa panoramica di indicazioni, sono comunque un ausilio per chi ha intenzione di intraprendere la carriera da programmatore. Basta infatti mettere una negazione ad ogni post e otterremo esattamente l’opposto di quello che il nostro amico voleva ottenere, a voi la scelta.
Durante lo sviluppo del codice, il programmatore serio, deve pensare anche quando dovrà gestire il software nei tempi futuri. Questo perchè lo sviluppo di un qualsiasi software non è mai un progetto a tempo determinato. Per tanti motivi, saranno applicate eccezioni progettuali che necessiteranno l'integrazione di nuove sezioni di codice o ci saranno comunque attività di manutenzione che sono inevitabilmente necessarie. Noi dovremmo fare in modo di rendere il codice non mantenibile, rendendo il più difficile possibile qualsiasi postumo tentativo di modifica.
Ma prima di parlare delle tecniche di non manutenibilità, parliamo un attimo di cosa vuol dire la parola mantenibilità. Per fare questo su web ho trovato questa rigorosa definizione:
Mantenibilità La mantenibilità è una misura della facilità con la quale un sistema può essere riparato una volta manifestatosi il malfunzionamento. In maniera più specifica, la mantenibilità è la probabilità M(t) che il sistema malfunzionante possa essere riportato al suo corretto funzionamento entro il periodo t. Essa è strettamente correlata con la disponibilità poiché tanto più è breve l'intervallo di ripristino del corretto funzionamento, tanto più elevata sarà la probabilità di trovare il sistema funzionante ad un dato istante temporale. Per il valore estremo M(0) = 1.0, il sistema in oggetto sarà sempre disponibile.
Ora per rendere codice ingestibile bisogna pensare come il futuro programmatore tenterà di approcciarsi alle migliaia di righe di codice che hai scritto. In pratica devi essere malvagiamente empatico. Si parte dalla premessa che, se tu fossi lo sventurato successore di qualche prolifico softwareista, non potrai sicuramente leggerti tutti i moduli sorgente cercando, magari di capirne il "senso" trascrivendone i diagrammi di flusso. L’unica cosa che potrai fare è quella di interpretare le sezioni di codice che ti servono per risolvere il problema contingente. Così il nostro target sarà quello di diminuire il più possibile il grado di comprensione del nostro codice o per esserè più rigorosi tenteremo di azzerare l’indice di mantenibilità
Nomenclatura delle variabili
Quidquid latine dictum sit, altum sonatur. - ( qualsiasi cosa detta in latino, sembrerà importante )
Le variabili vengono utilizzate nel 99% del nostro codice. La gestione e manipolazione delle variabili aumenta o diminuisce il grado di comprensione del software. In questa sezione consiederemo nel dettaglio come dare un nome specifico alle variabili.
Nomenclatura onomastica
Prendete un libro di nomi per bambini. Se sono stranieri è ancora meglio. Cercate quelli più caratteristici e applicateli alle variabili specifiche del vostro programma. Fred per esempio è un nome di variabile perfetto. Ci sono altri nomi particolarmente belli e facili da scrivere perchè sono sequenze di tasti come ad esempio asdf e qwerty e che vengono usati come nick dei forum...
Minimalista
Dichiarate le variabili con singole lettere x, y, vanno bene anche le accoppiate xy xz vanno benissimo anche i dittonghi ia ie etc. etc.
Scrittura erronea creativa
In questo caso le variabili vanno dichiarate con errori sintattici come, ad esempio, Idnice, Miusra o ad esempio gms o comnado. Possono essere chiamate anche dichiarazioni da refuso ( un mio collega ha scritto un modulo gms.cpp utilizzandolo su una decina di progetti contemporaneamente e lo maledico tutte le volte che ci penso... ).
Concetti astratti
Utilizzare termini il più generici possibile, tipo appunto: generico, esso, data, routine, o ad esempio anche dare il nome variabile è bellissimo
Acronimi
Definire il numero più ampio di acronimi possibile, ricordandosi ovviamente di evitare spiegazioni.
es:
ABPP SCRTD
Lingue straniere
Utilizzare definizioni di variabili in lingua straniera. Nel dettaglio quelle tipo Klingon, Hobbitese e Esperanto vanno benissimo.
Maisucolo randomico
In questo caso le variabili sono molto lunghe e approfittando che i parser dei compilatori sono sempre case sensitive si inserisce una maiuscola a caso, tipo VariaBileseCondaria()
Accenti
Mettiamo subito l’esempio così si capisce...
typedef struct { char ch; } ínt;
ìnt iPippo;
Lunghezza
I parser dei compilatori hanno un numero massimo di lettere verificate tipicamente 8. Francamente nei compilatori Microsoft non lo so se sono davvero 8. Comunque sia una volta dichiarate le costanti posso ottenere una cosa di questo tipo:
Numero_Massimo_diCaratteri
Numero_Massimo_diLettere
In entrambi i casi il compilatore le tratterrà allo stesso modo “Numero_M”
ASCII esteso
Utilizzare nella dichiarazione delle variabili i caratteri estesi ASCII ( ma questa è proprio un suicidio istantaneo dopo una decina di variabili scritte così, come farete davvero non lo so... auguri!)
Riferimenti matematici
Utilizzare nomi di variabili tipo barra/asterisco=addendo... .
Nomi accecanti
Utilizzare nomenclature non convenzionali
esempio:
Freud *superman=robocop+startrek
Lower Case l Looks a Lot Like the Digit 1
se non sei un anglofono copia il sottotitolo e traducilo con google capirai... subito ;)
Riusa le variabili globali come private o locali
Questa è una tipica possibilità del C che viene spacciata per un pregio, invece è un difetto. Dichiarare ad esempio una variabile globale Spare e una uguale all’interno delle funzioni rende difficoltoso seguire il flusso della variabile. Diciamo che, tendenzialmente, più variabili globali usi e più il codice diventa illeggibile e meno sicuro.
Cd wrttn wtht vwls s mch trsr
Anche le abbreviazioni abbassano notevolmente il grado di capibilità del codice, per ovvi motivi.
HOT Tips di offuscamento del codice
The longer it takes for a bug to surface, the harder it is to find.- Roedy Green
Una delle indicazioni più gettonate da parte dei team manager è quella di richiedere lo sviluppo di codice autoesplicante. Questo perchè si sono accorti che le modifiche a posteriori hanno l’effetto nefasto di disallineare i commenti dal codice. Come abbiamo già chiarito il codice autoesplicante è esattamente l’opposto di quello che stiamo facendo noi quindi....
Aggiungi tonnellate commenti
Il vero motivo per richiedere codice autoesplicante è che i commenti oltre a disallinearsi con il codice se scritti in in quantità diventano comunque difficili da leggere, spesso si ottiene l’effetto contrario a quello sperato. I commenti sono per definizione ambigui e fuorvianti. Così, scrivi commenti come se fossi il Proust degli schiacciatasti ( suffisso dato agli ingegneri informatici di origine ignota, l'equivalente per gli ingegneri elettronici è di spellafili per i maccanici girabulloni... e così via ognuno ha il suo). Scrivi, su tutte le linee di codice Includendo tutorial ed esempi di utilizzo del sorgente proprio come se fosse un manuale di istruzioni. Descrivi tutte le funzioni anche le più stupide.
es:
i++; // incrementa di uno
L’effetto minimo è quello di far perdere tempo a chi deve modificare il tuo codice, se poi hai la premura di inserire anche qualche minchiata quà e là si possono ottenere spiacevoli effetti collaterali.
Togli gli spazi
Elimina tutti gli spazi del codice in modo che le linee diventino cluster di caratteri. Queste possono diventare espressioni artistiche di pregievole livello.es:
l’effetto del codice senza spazi... è questo...
@ifdef PUMP_TIMEOUT // Verifico che il tempo trascorso dall'ultima attivazione // sia superiore a Tmin_Partenze ( campo K_Tmin_Partenze )timeCurrTimeDate = COleDateTime::GetCurrentTime();timeCurrTimeDate = timeCurrTimeDate - timeTime_Active_Pump; timeDelta.SetTime(0,lKTimer,0);if ( timeCurrTimeDate > timeDelta ){if (lTimerHold <= lTimerEs ){#endif lTimerHold = lTimerEs;iCurrIdx =(int) lId;#ifdef PUMP_TIMEOUT}}#endif
Inverti il senso
Se dalla descrizione della funzione deve tornare il valore vero, invertite il senso.
Es:
If ( isNegative( iValue ) )
isNegative è una funzione che ritorna uno stato booleano In questo caso il softwareista si aspetta che il parametro iValue sia vero quando è un valore negativo...
Mischia i linguaggi
Questo nella stesura del codice è davvero spiazzante. Se riuscite a mischiare le applicazioni fra Delphi, C++, Pyton, Php renderete il programma mooooolto illeggibili.
La notazione Ungara
In questo caso la notazione ungara è una vera arma nucleare nella guerra di offuscamento del codice. Basta mischiare i tipi di dato con la notazione in modo casuale.
es:
unsigned char pPointer;
Mascherare il codice=
Any fool can tell the truth, but it requires a man of some sense to know how to lie well.- Samuel Butler (1835 - 1902)
Incorrect documentation is often worse than no documentation.- Bertrand Meyer
Includere in una sezione di codice una parte di commento nel seguente modo
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 ];
}
Definizione di macro
Dichiarare una macro in modo che l’operazione vista non sia quella effettuata così come descritto nell'esempio seguente.
@define a=b a=0-b
Prolungamento di linea
la seguente dichiarazione di macro
@define local_var xy_z
potrebbe diventare
@define local_var xy\
_z // local_var OK
l’effetto è quello che il back slash estende la linea senza aggiungere line feed o carriage return. Il pregio di questa dichiarazione é che la variabile non può più essere trovata con uno strumento di ricerca come il find o il grep.
Overloading
Anche qui possiamo mischiare le operazioni approfittando di questa potente feature del C++. Ovviamente l’overloading migliore è quello che ti permette di differenziare le operazioni in modo tale che l’operazione ad esempio i = i + 5; dia un diverso risultato rispetto all'istruzione i += 5. L’overloading è lo stato dell’arte fra le operazioni di offuscamento del codice perchè agisce nel tempo, proprio mentre il nostro ignaro succedaneo continuerà a lavorare con il nostro software. Overloadare funzioni come il not “!” o la “new” è una vera libidine....
Doppio puntatore
Dal punto di vista algoritmico il doppio puntatore è una struttura dati che non serve a nulla. Ma fa tanto Nerd...
es:
int **ipp;
int i = 5;
int *ip1 = &i,ipp = &ip1;
CallBack
Questo è un tipo di riferimento a funzione, che proviene da una notoria sega mentale algoritimica informatica. Sostanzialmente le callback sono riferimenti a funzioni. Una tipica implementazione è quella di permettere ad una stessa funzione di gestire diverse strutture dati .
es: ( ripreso da Wikip)
@include <stdlib.h>
@include <string.h>
@define QUANTI_SOLDATI 100
struct TSoldato {
int grado;
char nome[80];
}
Esercito[QUANTI_SOLDATI];
int ConfrontaSoldati(const void *ptr1, const void *ptr2)
{
- const TSoldato *s1=(const TSoldato*)ptr1; // GNU GCC Compiler necessita della parola chiave "struct" prima di TSoldato // sia a sinistra che a destra dell'operatore di assegnazione
- const TSoldato *s2=(const TSoldato*)ptr2; // come sopra
- if(s1->grado!=s2->grado)
- return s1->grado - s2->grado;
- return s1->grado - s2->grado;
- return strcmp(s1->nome, s2->nome);
}
void OrdinaEsercito()
{
- qsort(Esercito, QUANTI_SOLDATI, sizeof(TSoldato), ConfrontaSoldati); // come sopra, all'interno della 'sizeof' prima di TSoldato
// scrivere la parola chiave "struct"
}
La funzione Ordina esercito richiama una funzione standard del C la qsort che ha bisogno di una funzione di callaback in grado di eseguire il confronto di un tipo di dato creato dall’utente. Come potete notare non ci sono riferimenti alla procedura e la ConfrontaSoldati ha un uso ambiguo come parametro.
Codice ricorsivo
Più codice ricorsivo implementi e meno leggibile diventa il codice. Ad esempio prendete un cronometro e misurate il tempo che impiegate a comprendere che tipo di operazione matematica esegue questa funzione ricorsiva.
int x (int n)
{
- if (n <= 1) return 1;
else
- return n * x (n-1);
}
Certo è, che se al nome della funzione avessi utilizzato un altro nome tipo, anzichè x che ne so “fatt”, allora il tempo di comprensione sarebbe stato nullo.
Funzioni monotone
Le funzioni dovrebbero essere tendenzialmente endofunzioni idempotenti. Prendo a piene mani dalla terminologia matematica forzando un po' il significato. Più parametri complessi vengono utilizzati e più l’indice di mantenibilità diminuisce.
Namespace
Le strutture/unioni e la definizione di tipo struttura/unione hanno diverse dichiarazioni. Puoi usarle insieme dichiarandole con lo stesso nome. Se poi sono anche simili ( ma non uguali ) è meglio.
typedef struct { char* pTr; size_t lEn; } snafu;
struct snafu { unsigned cNt char* pTr; size_t lEn; } A;
Macro inutili
Definisci funzioni che sembrano macro ma che non fanno niente. Magari commentando gli argomenti
- define fastcopy(x,y,z) /*xyz*/
fastcopy(array1, array2, size); /* does nothing */
Nascondi le istanze
Sempre con la define dichiara variabili del preprocessore cambiandole fra un header e l’altro
@define xxx global_var // in file std.h
@define xy_z xxx // in file ..\other\substd.h
@define local_var xy_z // in file ..\codestd\inst.h Define
@ifndef DONE
@ifdef TWICE // put stuff here to declare 3rd time around
- void g(char* str);
@define DONE
@else // TWICE
@ifdef ONCE // put stuff here to declare 2nd time around
- void g(void* str);
@define TWICE
@else // ONCE // put stuff here to declare 1st time around
- void g(std::string str);
@define ONCE
@endif // ONCE
@endif // TWICE
@endif // DONE
Ritorni di funzione
Nella stesura di una funzione canonica ci si aspetta che ci sia un unico punto di ritorno dalla funzione. Aumentare il numero di ritorni diminusice il livello di mantenibilità
Unità di misura
Mai spiegare l’unità di misura delle variabili numeriche
Gotchas
Il termine gotchas è una forma pecoreccia ritengo di origine americana della affermazione “I’ve got you”. Si traduce in un “Ho te” o anche “Ti ho preso” o “Ti tengo d’occhio” che nell’ambito della programmazione è evidentemente riferito al grande demone del programmatore che è il baco. Mai commentare una sezione di codice che potrebbe presentare problemi o potenziali bachi, perchè favorire così tanto il nostro successore ?
Documentare variabili
Mai documentare la dichiarazione delle variabili. Queste infatti sono il cuore della logica e gestiscono il flusso dei dati. Le variabili numeriche hanno range di valori legittimi, decimali , base tempi di aggiornamento, formattazione di visualizzazione, valore di inizializzazione. Spiegare una variabile significa spiegare l’algoritmo e il programma, state alla larga!
GOTO riesumato
La programmazione a GOTO contrastata dai puristi informatici è il pane quotidiano di chi programma in assembler. Si può scrivere codice strutturato anche utilizzando i goto ma se si abusa allora è tutta un'altra storia.
x ( char * pointer )
{
- loop:
- if( x++ < 10)
- goto loop;
- goto loop;
}
Offusca un sistema embedded
Nello sviluppo di sistemi embedded in real-time il controllo delle temporizzazioni è fondamentale. Più il sistema diventa complesso e più le sezioni di codice aumenteranno. Il tempo di esecuzione diventerà sempre meno marginale.
Loop bloccanti
Inserisci nel codice cicli operativi bloccanti. Ad esempio evita di utilizzare gli interrput per definire i timer che utilizzerai nel ciclo e usa sistematicamente delle funzioni di delay. Spostando o copiando sezioni di codice il software/firmware avrà risposte diverse.
es:delay(int sec){for ( i=0;i<sec;i++);}
Watchdog fantasma
Inserisci il watchdog in un interrupt timer. Se il loop principale del firmware per qualche motivo entra in dead lock il programma non se ne accorgerà.
Watchdog a schiovere
Anzichè avere un unico reset watchdog come sarebbe auspicabile, sparpagliali su tutto il codice a casaccio. Chi viene dopo di te, difficilmente prenderà la sonda dell’oscilloscopio per verificare i tempi. Se si convince che sei molto stretto con i tempi le modifiche diventano più complicate.
Awards dell’offuscamento del Codice
Se siete dei programmatori Java indubbiamente conoscerete questo link. Ci sono molti programmi commerciali che adottano tecniche varie di offuscamento del codice. Nel mondo è in atto una vera e propria guerra del software. Il fenomeno della pirateria informatica è poca cosa in confronto allo scontro fra le case madri come Google, Apple e Microsoft.Tutte indistintamente si copiano programmi compreso i bachi...Per limitare questi furti esistono una serie di tecniche e di programmi che hanno lo scopo di modificare il codice sorgente generante rendendolo difficile da gestire. ( link )
Ma c’è anche chi ne ha fatto diventare una vera è propria arte. In questo sito ( link ) c’è la possibilità di partecipare ad un concorso annuale dove i programmatori si sfidano nella gara di offuscamento del codice. Alcuni sono veramente incredibili...