Che cosa fa male alla manutenibilità? [duplicare]

73

Per qualcuno che non ha ancora molta esperienza nel mondo reale, la nozione di codice gestibile è un po 'vaga, anche se segue le regole tipiche delle buone pratiche. Intuitivamente posso dire che il codice può essere ben scritto, ma non è mantenibile se, per esempio, le informazioni sui cavi rigidi sono soggette a cambiamenti costanti, ma ho ancora difficoltà a guardare il codice e decidere se è manutenibile o meno.

Quindi la domanda è: cosa fa male la manutenibilità? Cosa dovrei cercare?

    
posta EpsilonVector 05.12.2011 - 08:58
fonte

17 risposte

94

Ci sono molti aspetti della manutenibilità, ma IMO i più importanti sono accoppiamento libero e alta coesione . Essenzialmente, quando lavori con un buon codice, puoi apportare piccole modifiche qua e là senza dover tenere a mente l'intera codebase. Con un codice errato, dovresti prendere in considerazione più cose: correggi qui e si interrompe altrove.

Quando hai più di 100 kLOC di codice davanti a te, questo è fondamentale. Le regole di "buona pratica" menzionate con successo come la formattazione del codice, i commenti, la denominazione delle variabili, ecc. Sono superficiali rispetto ai problemi di accoppiamento / coesione.

Il problema con l'accoppiamento / coesione è che non è facile misurare o vedere rapidamente. Ci sono alcuni tentativi accademici per farlo, e forse alcuni strumenti di analisi del codice statici, ma non tutto ciò che so di questo darebbe immediatamente una stima attendibile di quanto sia difficile avere tempo con il codice.

    
risposta data 05.12.2011 - 09:36
fonte
67

Codice duplicato :

Copy Paste è probabilmente l'operazione più costosa in termini di costi di manutenzione dei programmi.

Perché preoccuparsi di spostare questo codice su un componente comune, lo copierò e lo farò Sì ... il programmatore probabilmente ha risparmiato un'ora di lavoro o così facendo le cose in questo modo . Tuttavia dopo arriva un bug e ... sì, naturalmente, viene risolto solo nella metà del codice in esecuzione. Il che significa che in seguito verrà registrata una regressione che porterà a un'altra correzione DELLA STESSA COSA .

Questo sembra ovvio ma nel grande schema delle cose è insidioso. I programmatori sembrano buoni perché fanno di più. Il team manager di Dev sembra buono perché ha consegnato in tempo. E poiché il problema si trova solo molto più tardi, la colpa non si scontra mai con il vero colpevole.

Ho perso il conto di quante volte ho dovuto interrompere un rilascio a causa di tali "regressioni" Proprio ora ho perso un giorno intero rintracciando una correzione che era già stata eseguita, aggiustandola di nuovo e spingendo la nuova build attraverso i cerchi. Quindi sì, un'ora per un dev sei mesi fa (circa CAD 40 $) ha appena costato un giorno intero per 1 dev senior + mezza giornata per un dev junior + un ritardo di un giorno intero in un progetto già in ritardo ....

fai i conti ....

così il prossimo che tira su di me giuro che sarebbe meglio correre veloce perché ti sarò proprio dietro di lui !!!

    
risposta data 05.12.2011 - 09:10
fonte
52

Mentre il codice che infrange le regole nelle altre risposte è sicuramente peggio da mantenere, tutto il codice è difficile da mantenere , quindi meno te ne fai avere il meglio . I difetti sono strongmente correlati con la quantità di codice, quindi più codice hai più bug hai . Una volta che il codice ha superato una certa dimensione, non puoi più tenere il tuo intero disegno nella tua testa e le cose diventano molto più difficili. Infine, più codice alla fine significa più ingegneri, il che significa più costi, più problemi di comunicazione e più problemi di gestione.

Tuttavia, tieni presente che non è necessario scrivere codice più complesso, ma più breve. Il codice più gestibile è semplice e non contiene ridondanza per mantenerlo il più breve possibile. Delegare attività ad altri programmi o API può anche essere una soluzione praticabile fintanto che le chiamate API sono abbastanza semplici.

quindi, in poche parole, qualsiasi codice superfluo è un problema di manutenibilità

    
risposta data 08.11.2018 - 10:48
fonte
31

Ironicamente, i tentativi espliciti di "a prova di futuro" di un sistema spesso rendono più difficile la manutenzione.

Non è un hardcoding numeri magici o stringhe è una cosa, ma portalo oltre e inizi a inserire la logica nei file di configurazione. Il che significa che stai aggiungendo un parser e un interprete per un oscuro nuovo (e probabilmente mal progettato) linguaggio di programmazione al tuo carico di manutenzione.

Livelli di astrazione e indiretto aggiunti per nessun altro motivo che alcuni componenti potrebbero essere modificati in futuro e non vuoi dipendere direttamente da esso sono puro veleno di mantenimento e un ottimo esempio per < a href="http://en.wikipedia.org/wiki/YAGNI"> YAGNI . Le FAQ di SLF4J spiegano i problemi con questa idea fin troppo comune:

Given that writing a logging wrapper does not seem that hard, some developers will be tempted to wrap SLF4J and link with it only if it is already present on the classpath, making SLF4J an optional dependency of Wombat. In addition to solving the dependency problem, the wrapper will isolate Wombat from SLF4J's API ensuring that logging in Wombat is future-proof.

On the other hand, any SLF4J-wrapper by definition depends on SLF4J. It is bound to have the same general API. If in the future a new and significantly different logging API comes along, code that uses the wrapper will be equally difficult to migrate to the new API as code that used SLF4J directly. Thus, the wrapper is not likely to future-proof your code, but to make it more complex by adding an additional indirection on top of SLF4J, which is an indirection in itself.

L'ultima metà frase allude alla dolce ironia che lo stesso SLF4J è un wrapper di log a cui questo argomento si applica altrettanto bene (eccetto che sta facendo un tentativo credibile di essere l'unico wrapper di cui hai bisogno che può sedersi sopra e sotto tutte le altre comuni API di registrazione Java.

    
risposta data 19.02.2013 - 17:48
fonte
21

In base alla mia esperienza, un fattore importante nella manutenzione è consistenza . Anche se il programma funziona in modi strani e assurdi, è ancora più semplice mantenere se lo fa allo stesso modo ovunque. Lavorare con il codice che utilizza diversi schemi di denominazione, schemi di progettazione, algoritmi ecc. Per attività simili, a seconda di chi l'ha scritto quando, è un PITA importante.

    
risposta data 05.12.2011 - 09:54
fonte
18

Credo che il codice che non è Unit Tested fa male alla manutenibilità. Quando il codice è coperto dai test unitari, puoi essere sicuro che quando lo cambi sai che cosa stai rompendo perché i test unitari associati falliranno.

Avere test unitari insieme agli altri suggerimenti qui rende il tuo codice robusto e ti assicuri di sapere che quando cambi, esattamente quali sono gli impatti che stai avendo.

    
risposta data 05.12.2011 - 09:45
fonte
15

L'unico vero modo di imparare che cos'è un codice gestibile è quello di essere coinvolti nel mantenimento di una base di codice di grandi dimensioni (entrambe le correzioni di bug e richieste di funzionalità).

Imparerai a conoscere:

  • accoppiamento libero e alta coesione
  • codice duplicato
  • test di regressione e refactoring: con una suite di test di regressione efficiente, ti senti più sicuro nel cambiare le cose.
  • osservabilità e registrazione degli errori: se il software traccia cosa sta andando male, il bug si trova più facilmente.
  • documentazione accurata: non il tipo che viene scritto una volta all'inizio del progetto e mai modificato.
  • codice offuscato non documentato che assume la forma di un'idea di design apparentemente geniale o di ottimizzazioni ala radice quadrata inversa rapida .
  • tradizione orale trasmessa da una generazione di sviluppatori / manutentori a quella successiva.
risposta data 12.04.2017 - 09:31
fonte
5

Ho un paio di "incubi" che, sebbene non li avrei mai messi in cima a questa lista, hanno sicuramente causato un po 'di lotta.

Il mega oggetto / pagina / funzione : (Il cosiddetto " God Object " anti-pattern ): Mettere tutto nel metodo di un oggetto (principalmente il mondo Java), pagina (principalmente php e asp) , metodo (principalmente VB6).

Ho dovuto mantenere il codice da un programmatore che aveva in uno script php: codice html, logica aziendale, chiamate a mysql tutte incasinate. Qualcosa come:

foeach( item in sql_query("Select * from Items")) {
<a href="/go/to/item['id']">item['name']</a>
}

Affidarsi a funzioni oscure della lingua che potrebbero non essere ben note o stanno per diventare obsolete.

Era successo di nuovo con php e variabili preimpostate. Breve storia: per alcune impostazioni di php v4 un parametro di richiesta è stato assegnato automaticamente a una variabile. quindi link "magicamente" genererebbe $ id varaible con valore "42".

Questo è stato un incubo di manutenzione non solo perché non si poteva sapere da dove provenissero i dati ma anche perché php v5 lo ha rimosso completamente (+1), quindi le persone addestrate dopo quella versione, non avrebbero nemmeno capito cosa stava succedendo e perché .

    
risposta data 05.12.2011 - 13:22
fonte
4

Ciò che rende necessaria la manutenzione è che i requisiti sono un bersaglio mobile . Mi piace vedere il codice in cui sono stati anticipati possibili cambiamenti futuri e ho pensato a come gestirli se dovessero presentarsi.

Ho una misura di manutenibilità. Supponiamo che venga fornito un nuovo requisito o una modifica di un requisito esistente. Quindi vengono implementate le modifiche al codice sorgente, oltre a correggere eventuali bug correlati, fino a quando l'implementazione non è completa e corretta. Ora esegui una diff tra il codice base e il codice prima. Se questo include modifiche alla documentazione, includi quelle nella base del codice. Da diff puoi ottenere un conteggio N di quanti inserimenti, cancellazioni e sostituzioni di codice sono stati necessari per realizzare la modifica.

Il N più piccolo è, come media approssimativa rispetto alle modifiche dei requisiti passati e futuri, più il codice è mantenibile. Il motivo è che i programmatori sono canali rumorosi. Fanno errori. Più modifiche devono fare, più errori fanno, che diventano bug, e ogni bug è più difficile da trovare e correggere di quanto non lo sia in primo luogo.

Quindi sono d'accordo con le risposte che seguono seguire Non ripeti te stesso (DRY) o quello che viene chiamato cookie-cutter-code.

Sono anche d'accordo con il movimento verso i linguaggi specifici del dominio (DSL) forniti che riducono N. A volte le persone assumono che lo scopo di una DSL sia di essere "coder-friendly" trattando in "astrazioni di alto livello". Ciò non riduce necessariamente N. Il modo per ridurre N è quello di entrare in una lingua (che può essere semplicemente definita in base a una lingua esistente) che mappa più da vicino i concetti del dominio del problema.

La manutenibilità non significa necessariamente che qualsiasi programmatore possa semplicemente tuffarsi proprio dentro. L'esempio che uso dalla mia esperienza è Esecuzione differenziale . Il prezzo è una curva di apprendimento significativa. La ricompensa è una riduzione dell'ordine di grandezza nel codice sorgente per le finestre di dialogo dell'interfaccia utente, specialmente quelle che hanno elementi che cambiano dinamicamente. I cambiamenti semplici hanno N intorno a 2 o 3. I cambiamenti più complessi hanno N intorno a 5 o 6. Quando N è piccolo, la probabilità di introdurre bug è molto ridotta e dà l'impressione che "funzioni".

All'altro estremo, ho visto il codice in cui N era tipicamente nel range di 20-30.

    
risposta data 23.05.2017 - 14:40
fonte
2

Penso che la manutenibilità sia anche strettamente associata alla complessità del problema, che può essere una questione soggettiva. Ho visto situazioni in cui l'unico sviluppatore era in grado di mantenere con successo e crescere in modo consistente una grande base di codice, ma quando gli altri entrano nel suo posto, sembra un disordine irraggiungibile - solo perché hanno modelli mentali abbastanza diversi.

I modelli e le pratiche aiutano veramente (vedi altre risposte per un ottimo consiglio). Tuttavia, il loro abuso può portare a ancora più problemi quando la soluzione originale viene persa dietro facciate, adattatori e astrazioni non necessarie.

In generale, la comprensione viene con l'esperienza. Un ottimo modo per imparare è analizzare come gli altri hanno risolto problemi simili, cercando di trovare punti forti e deboli nella loro implementazione.

    
risposta data 05.12.2011 - 12:15
fonte
2

Questa non è davvero una risposta tanto quanto un lungo commento (perché queste risposte sono già state presentate).

Ho trovato che sforzarmi religiosamente per due fattori: interfacce pulite e utilizzabili e nessuna ripetizione hanno reso il mio codice molto migliore e nel tempo mi hanno reso un programmatore molto migliore.

A volte eliminare il codice ridondante è HARD, ti costringe a inventare alcuni schemi complicati.

Di solito analizzo ciò che DEVE cambiare, quindi rendi questo il mio obiettivo. Ad esempio, se si sta eseguendo una GUI su un client per aggiornare un database, a cosa è necessario aggiungere "Altro" (un altro controllo collegato al DB?) È necessario aggiungere una riga al DB e occorre la posizione del componente, il gioco è fatto.

Quindi, se questo è il tuo minimo indispensabile, non vedo NESSUN codice in questo - dovresti essere in grado di farlo senza toccare il tuo codice base. Questo è incredibilmente difficile da raggiungere, alcuni toolkit lo faranno (solitamente male), ma è un obiettivo. Quanto è difficile avvicinarsi? Non terribilmente. Posso farlo e lo ho fatto con codice zero in un paio di modi, uno è taggando i nuovi componenti della GUI con il nome della tabella nel DB, un altro creando un file XML - il file XML (o YAML se lo si odia XML) può essere davvero utile perché puoi collegare validatori e azioni speciali al campo, rendendo il pattern estremamente flessibile.

Inoltre, non ci vuole più tempo per implementare correttamente le soluzioni: dal momento in cui hai spedito la maggior parte dei progetti è effettivamente più economico.

Posso sottolineare che se ti affidi molto a Setters & Getters ("Bean Patterns"), Generics & Le classi interne anonime probabilmente NON codificano genericamente in questo modo. Negli esempi sopra, provare a forzare qualcuno di questi ti rovinerà davvero. Setter e amp; I getter ti obbligano a usare il codice per i nuovi attributi, Generics ti costringe a istanziare le classi (che richiede il codice) e amp; Le classi interiori anonime tendono a non essere facili da riutilizzare altrove. Se si sta veramente codificando genericamente, questi costrutti linguistici non sono male, ma possono rendere difficile la visualizzazione di un pattern. Per un esempio totalmente privo di senso:

user.setFirstName(screen.getFirstName());
user.setLastName(screen.getLastName());

Sembra buono, non proprio ridondante, almeno non in un modo che puoi risolvere, giusto? Ma ti fa aggiungere una linea quando vuoi aggiungere un "secondo nome", quindi è "codice ridondante"

user.getNameAttributesFrom(screen);

Non ha bisogno di un nuovo codice per questa attività - richiede semplicemente che alcuni attributi in "schermo" siano contrassegnati con un attributo "Nome", ma ora non possiamo copiare l'indirizzo, come su questo:

user.getAttributesFrom(screen, NAME_FIELDS, ADDRESS_FIELDS);

Più bello, un var-args ti permette di includere un gruppo di attributi (da un enum) per raccogliere dallo schermo - ancora devi modificare il codice per modificare i tipi di attributi che vuoi. Nota che "NAME_FIELDS" è una semplice istanza di enum - i campi in "schermo" sono etichettati con questo enum quando sono progettati per inserirli nelle categorie corrette - non esiste una tabella di conversione.

Attribute[] attrs=new Attributes[]{NAME_FIELDS, ADDRESS_FIELDS, FRIENDS_FIELDS};
user.getAttributesFrom(screen, attrs);

Ora hai capito dove stai solo cambiando "Dati". Questo è dove di solito lo lascio - con i dati nel codice - perché è "abbastanza buono". Il passo successivo consiste nell'esternalizzare i dati, se necessario, in modo da poterli leggere da un file di testo, ma raramente lo è. I refactoring "Roll up" sono molto simili una volta che hai preso l'abitudine, e quello che ho appena fatto lì ... ha creato un nuovo enum e pattern che finirà per scorrere in molti altri refactoring.

Infine, si noti che le buone soluzioni generiche per problemi come questo NON sono dipendenti dalla lingua. Ho implementato una soluzione no-code-per-loc per analizzare una GUI di testo dalla riga di comando di un router, aggiornandola sullo schermo e scriverla sul dispositivo - in VB 3. Ci vuole solo la dedica al principio di non scrivere codice ridondante, mai, anche se devi scrivere il doppio del codice per farlo!

Anche l'interfaccia pulita (completamente Factored e non consente il passaggio di materiale illegale) è importante. Quando si calcola correttamente un'interfaccia tra due unità di codice, è possibile manipolare il codice su entrambi i lati dell'interfaccia con impunità e consentire ai nuovi utenti di implementare in modo pulito le interfacce.

    
risposta data 05.12.2011 - 18:26
fonte
2

oltre ai modelli citati una delle cose più importanti è

codice leggibile , ogni singola riga di codice inserita in un codebase verrà letta più di 100 volte e mentre le persone rintracciano i bug che non vuoi che trascorrano 30 minuti per cercare di capire cosa lo fa ogni volta.

Quindi non cercare di essere intelligente con il codice "ottimizzato" a meno che non sia realmente un collo di bottiglia e poi commentarlo e inserire il codice originale leggibile (e funzionante) nei commenti (o percorso di compilazione alternativo per i test) come riferimento per cosa dovrebbe fare.

questo include la funzione descrittiva, i nomi di parametri e variabili e i commenti che spiegano PERCHÉ un determinato algoritmo viene scelto su un altro

    
risposta data 05.12.2011 - 23:16
fonte
1

Quasi tutto ciò che viola alcuni buoni principi di sviluppo del software fa male alla manutenibilità. Se guardi del codice e vuoi valutare quanto è gestibile, puoi scegliere alcuni principi specifici e controllare quanto li viola .

Il principio DRY è un buon punto di partenza: quante informazioni vengono ripetute nel codice? YAGNI è anche molto utile: quante funzionalità ci sono che non sono necessarie?

Se fai qualche ricerca su DRY e YAGNI troverai altri principi, che sono applicabili come principi generali di sviluppo del software. Se stai utilizzando un linguaggio di programmazione orientato agli oggetti e vuoi essere più specifico, i principi SOLID dare alcune buone linee guida per una buona progettazione orientata agli oggetti.

Il codice che viola uno di quei principi tende a essere meno mantenibile. Ho posto l'accento su tend qui, perché - generalmente parlato - ci sono situazioni in cui è lecito (leggermente) violare qualsiasi tipo di principio, specialmente se è stato fatto per ragioni di manutenibilità.

Ciò che aiuta anche a cercare odori di codice . Dai un'occhiata a Refactoring - Migliorare la progettazione del codice esistente di Martin Fowler. Qui puoi trovare esempi di odori di codice, che migliorano il tuo senso per un codice meno gestibile.

    
risposta data 05.12.2011 - 11:05
fonte
1

Troppe funzioni.

Le funzionalità stesse per natura danneggiano la manutenibilità perché è necessario scrivere codice aggiuntivo per implementarle, ma un'applicazione senza funzionalità è inutile, quindi aggiungiamo funzionalità. Tuttavia, tralasciando le funzioni non necessarie puoi mantenere il tuo codice più gestibile.

    
risposta data 05.12.2011 - 16:29
fonte
-1
  1. Mancanza di test unitari appropriati
  2. Mancanza di test di integrazione appropriati
  3. Mancanza di test prestazionali di benchmarking adeguati
  4. YAGNI

3 e 4 sono, secondo la mia esperienza, i trasgressori più comuni nei sistemi di vita reale. Il fatto di non avere una suite di performance / loadtesting adeguata rende impossibile valutare l'impatto che i cambiamenti futuri avranno sulle prestazioni e sulla stabilità e causerà un degrado molto più rapido e costringerà i refactoring più costosi più spesso. YAGNI (quello che viene chiamato "spreco" nel vocabolario Lean ) renderà un sistema molto più difficile da refactoring e manterrà molto tempo sprecato (gioco di parole) sul mantenimento del codice e retrocompatibilità per funzionalità che non sono nemmeno usati.

    
risposta data 05.12.2011 - 11:23
fonte
-2

L'approccio migliore per la manutenibilità dalla mia esperienza è di avere il minor numero possibile di codice e di spingere il lavoro agli altri (ad esempio i fornitori di software) che fanno la manutenzione per te.

Preferisco avere il minor numero possibile di fonti personalizzate e spingere il più possibile su fogli Excel, XML o file di proprietà che rappresentano i dati aziendali e fornire lettori semplici o utilizzare uno strumento ETL per eseguire la conversione al codice dell'applicazione.

Cerco anche di trovare qualcosa che sia basato su standard (cioè nessun singolo venditore) se possibile. Tuttavia, ci sono alcune cose che non possiamo evitare ad es. Spring for dependency injection prima di JEE6 e Hibernate per ORM prima di JEE5, log4j / commons-logging / slf4j prima di Java 1.4. Scegliendo qualcosa di standard, c'è una maggiore possibilità di cambiare fornitore, specialmente se si diventa defunto o troppo costoso e si evitano i problemi del classpath.

Naturalmente se sei il fornitore che fornisce il software è un'altra questione. Tuttavia, se hai a che fare con situazioni di vita reale a volte è meglio investire denaro nel problema piuttosto che in abilità e tempo.

    
risposta data 05.12.2011 - 14:02
fonte
-3

Tutto ciò che non è semplice da comprendere.

La forza più distruttiva per qualsiasi tipo di codice è la manutenzione. Sfortunatamente, anche la manutenzione è assolutamente necessaria. Se il manutentore non capisce rapidamente il codice che instradano attorno ad esso, è altrettanto distruttivo o quasi certamente lo spezza.

Per riportare erroneamente Einstein: Rendi il più semplice possibile - ma non più semplice.

    
risposta data 06.12.2011 - 10:29
fonte

Leggi altre domande sui tag