Esistono linee guida comunemente accettate su come scrivere la C moderna?


13

Ho un forte background Java / Groovy e sono stato assegnato a un team che mantiene una base di codice C abbastanza grande per un software amministrativo.

Alcuni punti critici, come la gestione dei BLOB nel database o la generazione di report in PDF ed Excel sono stati esternalizzati al servizio Web Java.

Tuttavia, come sviluppatore Java, sono un po 'confuso da alcuni aspetti del codice:

  • è dettagliato (specialmente quando si tratta di "eccezione")
  • ci sono molti metodi enormi (molti metodi con oltre 2000 linee)
  • non ci sono strutture dati avanzate (mi mancano molto List, Set e Map)
  • nessuna separazione delle preoccupazioni (SQL è mescolato con gioia in tutto il codice)

Di conseguenza, ritengo che il business sia nascosto in tonnellate di codice tecnico e il mio cervello, modellato con Object Oriented e un pizzico di programmazione funzionale, non è a mio agio.

Il lato positivo del progetto è che il codice è diretto: non esiste un framework, nessuna manipolazione del codice byte in fase di esecuzione, nessun AOP. E il server può rispondere simultaneamente a oltre 10000 utenti con una sola macchina usando meno memoria di quanto java abbia bisogno per sputare "ciao mondo".

Voglio imparare a scrivere il codice C di conseguenza secondo i principi moderni comunemente accettati. Esistono principi comunemente accettati su come la C moderna dovrebbe essere scritta e strutturata?

Qualcosa di simile all'equivalente del libro "Effective Java", ma per C.

Modifica alla luce di risposte e commenti:

  • Proverò ad adattare la mia mentalità al codice C e non tenterò di rispecchiarlo su OOP.
  • Ho iniziato a leggere e leggere le guide dello stile di codifica consigliate dal commento (The GNU Coding Standards e The Linux Kernel Coding Style).
  • Proverò quindi a proporre questo stile di codice ai miei colleghi. La parte più difficile potrebbe essere quella di convincere i colleghi che un metodo enorme potrebbe essere suddiviso in parti più piccole e che ripetere le stesse 4 righe del codice di gestione degli errori potrebbe essere evitato con l'aiuto di un metodo.

5
L'applicazione ha davvero bisogno di essere modernizzata o pensi semplicemente che lo faccia perché il modo in cui è stata scritta non ha familiarità?
Blrfl,


1
@Blrfl, ritengo che l'applicazione sia stata scritta con standard obsoleti. Voglio solo sapere cosa è lo standard di oggi (2016) per (amministrativo) C. Se ce n'è uno. Non voglio riformattare o rimodellare l'app corrente, voglio avere un'idea di come dovrei scrivere la parte successiva del codice.
Guillaume,


3
@antlersoft: una funzione di 2.000 righe che fa una lunga lista di cose semplici, una dopo l'altra, non è assolutamente un problema e non ha bisogno di una scusa. Per favore non rispondere con argomenti circolari come "non dovresti scrivere 2000 funzioni di linea perché non dovresti scrivere 2000 funzioni di linea".
gnasher729,

Risposte:


14

Posso leggere dalla tua domanda che il problema non è che il codice è vecchio C, ma solo una cattiva programmazione. La maggior parte dei problemi che hai citato come la verbosità, enormi funzioni di linea 2000+ o nessuna separazione di preoccupazioni è applicabile a qualsiasi linguaggio, C o Java.

La verbosità è stata menzionata nel contesto della gestione degli errori. Non hai fornito un esempio, quindi posso solo ricordare che anche la gestione degli errori è un codice . Non ci sono scuse per sezioni ripetitive del codice della caldaia. Fattorizzalo; in una funzione o (se non vale la pena creare una funzione separata) eseguire il goto Error;modello e spostare la gestione degli errori e la pulizia delle risorse in una Error:sezione nella parte inferiore della funzione.

Se passare l'errore nella catena di chiamate sembra essere il problema, chiediti: la funzione lassù ha davvero bisogno di sapere che un ragazzino quaggiù ha avuto un problema? I meccanismi di eccezione integrati in una lingua lo rendono facile, ma in generale è meglio gestire le eccezioni in anticipo (in qualsiasi lingua) in modo che la condizione di errore non inquini la logica del codice di alto livello. E se la funzione lassù ha davvero bisogno di sapere, ci sono modi per emulare eccezioni con setjmpe longjmp.

Penso che l'unico vero problema legato alla C menzionato sia la mancanza di contenitori standard. Mentre Setin generale può essere sostituito con un array ordinato e Map(per la maggior parte) con un array di coppie o un struct(se si conosce la chiave impostata prima, map[key] = valuesi trasforma in s.key = value), ma il fatto è che non esiste un contenitore di array dinamico nello standard biblioteca. In C99 puoi almeno dichiarare un array a lunghezza variabile sullo stack ( int array[len]) ma devi calcolare in lenanticipo (di solito non difficile) e ovviamente non puoi restituirlo come qualsiasi oggetto allocato nello stack . La maggior parte dei progetti finisce per scrivere il proprio contenitore di array dinamico o adottarne uno open source.

In una nota conclusiva, vorrei sottolineare che ci sono stato. Sono stato il programmatore Java che si è trasferito in C ++ e in puro C. Vorrei consigliare "leggere il libro X per imparare la buona C" ma non ce n'è proprio come non ce n'è per Java. La strada da percorrere è assorbire tutte le complessità della lingua e della libreria standard; google molto, leggi molto e codice molto finché non inizi a pensare in C. Cercare di scrivere cose in C come faresti in Java è frustrante come provare a scrivere una frase in una lingua straniera con parole tradotte direttamente da tua madre lingua; sia tu che il lettore ti farai rabbrividire. La buona notizia è che l'apprendimento di una buona programmazione è lento ma l'apprendimento di un'altra lingua è veloce. quindi se scrivi un codice decente in Java,


1
Tutto sommato, questa è davvero una buona risposta. Vorrei solo obiettare a vedere setjmp()/ longjmp()come uno strumento valido: non tenta nemmeno di eseguire alcuna pulizia. Qualsiasi allocazione verrà trapelata, qualsiasi blocco bloccato non verrà rilasciato, qualsiasi file aperto non verrà chiuso e qualsiasi incoerenza di dati transitori diventerà permanente. IMHO, questa coppia di funzioni è fondamentalmente il peggior hack mai inventato, con la sola giustificazione che era possibile implementarlo. Alla fine, esiste davvero un solo modo valido per eseguire la gestione degli errori in C: codici di errore espliciti.
cmaster - ripristina monica

@cmaster yea. Personalmente, setjmp/longjmpsembra un pesce fuor d'acqua in C e non li ho mai usati. Mi sono sentito obbligato a includerli solo a causa dei numerosi tutorial / librerie su Internet per imitare le eccezioni, quindi ho pensato che ci sono persone che la usano davvero.
Un gufo

7

Il lato positivo del progetto è che il codice è diretto: non esiste un framework, nessuna manipolazione del codice byte in fase di esecuzione, nessun AOP. E il server può rispondere simultaneamente a oltre 10000 utenti con una sola macchina usando meno memoria di quanto java abbia bisogno per sputare "ciao mondo".

Ti consiglierei di essere cauto sul fatto che questo valga la pena del tuo tempo e dei soldi dell'azienda per spendere risorse per "modernizzare" un software funzionante con una bassa complessità del codice e chi funziona bene. C'è un alto potenziale che introdurrai tu stesso nuovi bug, soprattutto perché sembra essere un sistema con cui non hai familiarità.

Se vuoi ancora percorrere questa strada, suggerirei quanto segue:

  • Crea (o genera) un diagramma di stato del software / codice
  • Immergiti nel codice e crea un elenco delle parti più complesse o critiche del codice rispettivamente
  • Trova qualcuno che sia a conoscenza di quella base di codice e chiedi loro perché è stato costruito in questo modo e cosa è noto per causare problemi
  • Scrivi la documentazione da ciò che hai imparato

A questo punto, deciderai se vale la pena esplorare o meno. Se la cultura della tua azienda non premia il fallimento, ottieni il via libera da un superiore o da un manager.

  • Compartimentalizza i diversi elementi costitutivi del software e scrivi test unitari per ciascuno.
  • Scorrere fino a quando non è possibile incollare insieme i diversi moduli
  • Eseguire ulteriori test che simulano l'interazione dell'utente reale (stress test ecc.)

Penso che sia una buona tabella di marcia e ti porti ovunque tu abbia bisogno. Senza conoscere i dettagli di questo progetto è difficile aiutarti molto. Si prega di non gettare il mio disclaimer come eccessivamente allarmista. Tonnellate di programmatori eccellenti hanno battuto la polvere cercando di riscrivere un progetto esistente nella loro lingua preferita o usando strumenti "moderni". È una decisione che deve essere attentamente ponderata e ti esorto a non fare il ladro e farlo da solo senza il supporto gestionale o l'assistenza dei tuoi colleghi.


2
Mi rendo conto che la mia domanda non era affatto chiara. Non voglio riformattare il codice. Affatto. Voglio mantenere la base di codice esistente così com'è. Tuttavia, voglio imparare a scrivere la C moderna per la nuova funzionalità. E qui mi sono perso. La maggior parte della documentazione che ho trovato riguarda il modo in cui scrivere il codice in C, non il modo di scrivere "moderno" C. Forse non esiste qualcosa come il "moderno" C ...
Guillaume

1

Se preferisci un linguaggio di livello superiore, ci sono alcune lingue come C ++ o Objective-C che possono essere facilmente combinate con il codice C.

In alternativa, C e C ++ sono ragionevolmente compatibili. Potresti essere in grado di compilare l'intera base di codice come C ++ con poche modifiche: avrai la variabile occasionale denominata "class" o "template" che dovrai rinominare, ma in pratica sarà tutto. (sizeof ('a') è diverso in C e C ++, ma non credo di averlo mai usato).

Se segui questa strada, considera che il prossimo manutentore potrebbe non essere troppo fluente con C ++. Non lasciarti trasportare. Approfitta del C ++, ma solo fino a che un programmatore C può facilmente capirlo.


1
Non sono d'accordo qui. C e C ++ sono linguaggi distinti e alcuni codici richiesti da un compilatore C ++ (che esplicitamente esegue il cast del valore restituito malloc) sono considerati cattive pratiche in C. Il significato di consted inlineè anche molto diverso tra C e C ++, e ovviamente C ++ non capisce __restrict. Non trattare le lingue come intercambiabili, neppure nel sottoinsieme di fonti che si compongono in entrambe.
Angew non è più orgoglioso di SO

1

Fondamentalmente, scrivere un buon codice C equivale a scrivere un buon codice C ++ o Java: vuoi una classe, usa a struct. Vuoi l'eredità, includi la base structcome primo membro senza nome. Volete funzioni virtuali, aggiungete un puntatore a uno staticostruct di puntatori a funzione. E così via, ecc. È esattamente ciò che fa C ++ sotto il cofano, l'unica differenza è che è esplicito in C. Quindi, puoi fare una programmazione perfettamente orientata agli oggetti in C, sembra solo un po 'diversa e più calda rispetto a ciò che sono abituati a.

Il punto è che una buona programmazione riguarda i paradigmi, non le caratteristiche del linguaggio. È vero, è sempre bello se le funzionalità della tua lingua forniscono un buon supporto per i paradigmi che vuoi usare, ma le funzionalità della lingua non sono un requisito. Una volta che lo capisci, puoi scrivere un buon codice praticamente in qualsiasi lingua (a parte alcune lingue esoteriche come brainfuck o INTERCAL, cioè).

Ovviamente, il problema rimane che la libreria C standard non contiene nessuna di quelle eleganti classi di contenitori a cui siete abituati. Sfortunatamente, ciò significa che sarà necessario utilizzare il proprio o aggirare questa mancanza mediante l'uso di array allocati dinamicamente. Ma scommetto che presto scoprirai che tutto ciò di cui hai veramente bisogno sono matrici dinamiche ( malloc()) e liste / alberi collegati che sono implementati tramite membri puntatore all'interno delle tue classi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.