Gibt es allgemein anerkannte Richtlinien zum Schreiben von modernem C?


13

Ich habe einen starken Java / Groovy-Hintergrund und wurde einem Team zugewiesen, das eine ziemlich große C-Codebasis für eine Verwaltungssoftware unterhält.

Einige Probleme, wie das Behandeln von Blobs in der Datenbank oder das Generieren von Berichten in PDF und Excel, wurden in den Java-Webdienst ausgelagert.

Als Java-Entwickler bin ich jedoch ein bisschen verwirrt über einige Aspekte des Codes:

  • es ist wortreich
  • Es gibt viele große Methoden (Methode mit mehr als 2000 Zeilen)
  • Es gibt keine fortgeschrittenen Datenstrukturen (ich vermisse List, Set und Map sehr)
  • keine Trennung von Bedenken (SQL wird fröhlich rund um den Code gemischt)

Infolgedessen habe ich das Gefühl, dass das Geschäft in Tonnen von technischem Code verborgen ist und mein Gehirn, das mit objektorientierter und einer Prise funktionaler Programmierung geformt wurde, sich nicht wohl fühlt.

Die gute Seite des Projekts ist, dass der Code einfach ist: Es gibt kein Framework, keine Manipulation des Bytecodes zur Laufzeit, kein AOP. Und der Server kann gleichzeitig über 10.000 Benutzern mit einer einzigen Maschine antworten, indem er weniger Arbeitsspeicher benötigt, als Java zum Spucken von "Hallo Welt" benötigt.

Ich möchte lernen, wie man C-Code entsprechend den allgemein anerkannten modernen Prinzipien schreibt. Gibt es allgemein anerkannte Prinzipien, wie modernes C geschrieben und strukturiert werden sollte?

Etwas wie das Äquivalent des Buches 'Effective Java', aber für C.

Bearbeiten Sie im Licht der Antworten und Kommentare:

  • Ich werde versuchen, meine Denkweise an C-Code anzupassen und nicht, sie in OOP zu spiegeln.
  • Ich habe begonnen, die empfohlenen Coding-Style-Guides aus dem Kommentar (The GNU Coding Standards und The Linux Kernel Coding Style) zu lesen.
  • Ich werde dann versuchen, meinen Mitarbeitern diesen Codestil vorzuschlagen. Der schwierigste Teil könnte darin bestehen, die Mitarbeiter davon zu überzeugen, dass eine große Methode in kleinere Teile aufgeteilt werden kann und dass die Wiederholung derselben vier Zeilen Fehlerbehandlungscode mithilfe einer Methode vermieden werden kann.

5
Muss die Anwendung tatsächlich modernisiert werden, oder glaubst du nur, dass dies der Fall ist, weil die Art und Weise, wie sie geschrieben wurde, unbekannt ist?
Blrfl


1
@Blrfl, ich habe das Gefühl, dass die Anwendung mit veraltetem Standard geschrieben wurde. Ich möchte nur wissen, was heute (2016) Standard für (administrative) C ist. Wenn es einen gibt. Ich möchte die aktuelle App weder umgestalten noch umgestalten. Ich möchte eine Vorstellung davon haben, wie ich den nächsten Teil des Codes schreiben soll.
Guillaume


3
@antlersoft: Eine 2.000-Zeilen-Funktion, die eine lange Liste einfacher Dinge nach der anderen ausführt, ist absolut kein Problem und bedarf keiner Entschuldigung. Bitte antworten Sie nicht mit Zirkelargumenten wie "Sie sollten nicht 2.000 Zeilenfunktionen schreiben, weil Sie nicht 2.000 Zeilenfunktionen schreiben sollten".
gnasher729

Antworten:


14

Ich kann Ihrer Frage entnehmen, dass das Problem nicht darin besteht, dass der Code altes C ist, sondern nur eine schlechte Programmierung. Die meisten Probleme, die Sie erwähnt haben, wie Ausführlichkeit, über 2000 Zeilenfunktionen oder keine Trennung von Bedenken, gelten für jede Sprache, C oder Java gleichermaßen.

Ausführlichkeit wurde im Zusammenhang mit der Fehlerbehandlung erwähnt. Sie haben kein Beispiel angegeben, sodass ich nur daran erinnern kann, dass Fehlerbehandlungscode auch Code ist . Es gibt keine Entschuldigung für sich wiederholende Abschnitte des Boilerplate-Codes. Faktor es aus; Entweder für eine Funktion oder (wenn es sich nicht lohnt, eine separate Funktion zu erstellen) führen Sie das goto Error;Muster aus und verschieben Sie die Fehlerbehandlung und die Ressourcenbereinigung in einen Error:Abschnitt am unteren Rand der Funktion.

Wenn die Weitergabe des Fehlers über die Anrufkette das Problem zu sein scheint, fragen Sie sich: Muss die Funktion dort oben wirklich wissen, dass ein kleiner Kerl hier unten ein Problem hatte? Die in einer Sprache integrierten Ausnahmemechanismen machen dies einfach. Im Allgemeinen ist es jedoch besser, Ausnahmen frühzeitig (in jeder Sprache) zu behandeln, damit die Fehlerbedingung die Logik von Code auf hoher Ebene nicht beeinträchtigt. Und wenn die Funktion dort oben wirklich wissen muss, gibt es Möglichkeiten, Ausnahmen mit setjmpund zu emulierenlongjmp .

Ich denke, das einzige wirklich C-bezogene Problem, das erwähnt wird, ist das Fehlen von Standardcontainern. Zwar Setkann im Allgemeinen durch ein sortiertes Array und Map(zum größten Teil) durch ein Array von Paaren oder ein struct(wenn Sie den Schlüsselsatz vorher kennen, map[key] = valuewird er zu s.key = value) ersetzt werden, aber die Tatsache ist, dass der Standard keinen dynamischen Array-Container enthält Bibliothek. In C99 können Sie mindestens ein Array mit variabler Länge für den Stack deklarieren ( int array[len]), dies muss jedoch im lenVoraus berechnet werden (normalerweise nicht schwer), und Sie können es natürlich nicht als stapelzugeordnetes Objekt zurückgeben. Die meisten Projekte schreiben ihren eigenen dynamischen Array-Container oder übernehmen einen Open-Source-Container.

Abschließend möchte ich darauf hinweisen, dass ich dort war. Ich war der Java-Programmierer, der zu C ++ und reinem C übergegangen ist. Ich würde empfehlen, "Buch X zu lesen, um gutes C zu lernen", aber es gibt keine, so wie es keine für Java gibt. Der Weg vorwärts ist, alle Feinheiten der Sprache und der Standardbibliothek in sich aufzunehmen. googeln Sie viel, lesen Sie viel und programmieren Sie viel, bis Sie anfangen, in C zu denken. Der Versuch, Dinge in C zu schreiben, wie Sie es in Java tun würden, ist ebenso frustrierend wie der Versuch, einen Satz in einer Fremdsprache mit Wörtern zu schreiben, die direkt von Ihrer Mutter übersetzt wurden Zunge; Sie und der Leser werden zusammenzucken. Die gute Nachricht ist, dass es langsam ist, gute Programme zu lernen, aber eine andere Sprache schnell zu lernen. Also, wenn Sie anständigen Code in Java schreiben,


1
Alles in allem ist dies eine wirklich gute Antwort. Ich würde nur ablehnen, setjmp()/ longjmp()als gültiges Werkzeug zu sehen: Es wird nicht einmal versucht, eine Bereinigung durchzuführen. Allfällige Zuordnungen gehen verloren, gehaltene Sperren werden nicht aufgehoben, geöffnete Dateien werden nicht geschlossen und vorübergehende Dateninkonsistenzen werden dauerhaft. Meiner Meinung nach ist dieses Funktionspaar im Grunde der schlimmste Hack, der jemals erfunden wurde, mit der einzigen Rechtfertigung, dass es möglich war, es zu implementieren. Letztendlich gibt es in C nur eine gültige Methode zur Fehlerbehandlung: explizite Fehlercodes.
cmaster

@cmaster ja. Persönlich setjmp/longjmpscheint ein Fisch in C kein Wasser mehr zu haben und ich habe ihn nie benutzt. Ich fühlte mich gezwungen, sie nur wegen der zahlreichen Tutorials / Bibliotheken im Internet aufzunehmen, um Ausnahmen nachzuahmen, also dachte ich, dass es Leute gibt, die sie tatsächlich nutzen.
Eine Eule

7

Die gute Seite des Projekts ist, dass der Code einfach ist: Es gibt kein Framework, keine Manipulation des Bytecodes zur Laufzeit, kein AOP. Und der Server kann gleichzeitig über 10.000 Benutzern mit einer einzigen Maschine antworten, indem er weniger Arbeitsspeicher benötigt, als Java zum Spucken von "Hallo Welt" benötigt.

Ich würde Ihnen empfehlen, vorsichtig zu sein, ob dies Ihre Zeit und das Geld des Unternehmens wert ist, um Ressourcen für die "Modernisierung" einer funktionierenden Software mit geringer Codekomplexität auszugeben und wer eine gute Leistung erbringt. Es ist sehr wahrscheinlich, dass Sie selbst neue Bugs einführen, zumal es ein System zu sein scheint, mit dem Sie nicht vertraut sind.

Wenn Sie diesen Weg immer noch einschlagen möchten, würde ich Folgendes vorschlagen:

  • Erstellen (oder generieren) Sie ein Zustandsdiagramm der Software / des Codes
  • Tauchen Sie in den Code ein und erstellen Sie eine Liste der komplexesten bzw. kritischsten Teile des Codes
  • Suchen Sie jemanden, der sich mit dieser Codebasis auskennt, und fragen Sie ihn, warum sie auf diese Weise erstellt wurde und was bekanntermaßen Probleme verursacht
  • Schreiben Sie die Dokumentation von dem, was Sie gelernt haben

An diesem Punkt werden Sie entscheiden, ob es sich lohnt, dies zu erkunden. Wenn die Unternehmenskultur einen Misserfolg nicht belohnt, erhalten Sie grünes Licht von einem höheren oder einem Manager.

  • Unterteilen Sie die verschiedenen Bausteine ​​der Software und schreiben Sie Unit-Tests für jeden.
  • Iterieren Sie, bis Sie die verschiedenen Module zusammenkleben können
  • Weitere Tests durchführen, die eine echte Benutzerinteraktion simulieren (Stresstests usw.)

Ich denke, das ist eine ziemlich gute Straßenkarte und bringt Sie dorthin, wo Sie es brauchen. Ohne die Einzelheiten dieses Projekts zu kennen, ist es schwierig, Ihnen viel zu helfen. Bitte verwerfen Sie meinen Haftungsausschluss nicht als übermäßig alarmierend. Unzählige exzellente Programmierer haben den Staub überwunden, indem sie versucht haben, ein bestehendes Projekt in ihre Lieblingssprache umzuschreiben oder "moderne" Tools zu verwenden. Das ist eine Entscheidung, die sorgfältig durchdacht werden muss, und ich fordere Sie dringend auf, nicht auf eigene Faust zu handeln, ohne die Unterstützung des Managements oder die Unterstützung Ihrer Kollegen.


2
Mir ist klar, dass meine Frage überhaupt nicht klar war. Ich möchte den Code nicht überarbeiten. Überhaupt. Ich möchte die vorhandene Codebasis so beibehalten, wie sie ist. Ich möchte jedoch lernen, wie man modernes C für die neue Funktion schreibt. Und hier bin ich verloren. Die meiste Dokumentation, die ich gefunden habe, befasst sich mit dem Codieren in C und nicht mit dem Schreiben von "modernem" C. Vielleicht gibt es kein "modernes" C ...
Guillaume,

1

Wenn Sie eine höhere Sprache bevorzugen, gibt es einige Sprachen wie C ++ oder Objective-C, die sich problemlos mit C-Code mischen lassen.

Alternativ sind C und C ++ einigermaßen kompatibel. Möglicherweise können Sie die gesamte Codebasis mit wenigen Änderungen einfach als C ++ kompilieren - Sie haben gelegentlich die Variable "class" oder "template", die Sie umbenennen müssen. In der Praxis ist dies jedoch alles. (sizeof ('a') unterscheidet sich in C und C ++, aber ich glaube nicht, dass ich das jemals benutzt habe).

Wenn Sie diesen Weg gehen, denken Sie daran, dass der nächste Betreuer mit C ++ möglicherweise nicht zu fließend ist. Lass dich nicht mitreißen. Nutzen Sie C ++, aber nur so weit, dass ein C-Programmierer es leicht verstehen kann.


1
Ich muss hier nicht zustimmen. C und C ++ sind verschiedene Sprachen, und einige Code durch einen C ++ Compiler erforderlich (explizit den Rückgabewert von Gießen malloc) ist schlechte Praxis in C. Die Bedeutung betrachtet constund inlineist auch sehr unterschiedlich zwischen C und C ++, und natürlich C ++ nicht versteht __restrict. Behandle die Sprachen nicht als austauschbar, auch nicht in der Teilmenge der Quellen, die in beiden kompiliert werden.
Angew ist nicht mehr stolz auf SO

1

Grundsätzlich ist das Schreiben von gutem C-Code dasselbe wie das Schreiben von gutem C ++ - oder Java-Code: Sie möchten eine Klasse, verwenden Sie a struct. Wenn Sie eine Vererbung wünschen, schließen Sie die Basis structals namenloses erstes Mitglied ein. Wenn Sie virtuelle Funktionen möchten, fügen Sie einen Zeiger zu einem statischen structFunktionszeiger hinzu. Und so weiter usw. Genau das macht C ++ unter der Haube. Der einzige Unterschied besteht darin, dass es in C explizit ist. Somit können Sie in C perfekt objektorientiert programmieren sind gewöhnt an.

Der Punkt ist, dass es bei guter Programmierung um Paradigmen geht, nicht um Sprachmerkmale. Es ist zwar immer schön, wenn Ihre Sprachfunktionen die gewünschten Paradigmen gut unterstützen, die Sprachfunktionen jedoch nicht erforderlich sind. Sobald Sie dies bemerken, können Sie guten Code in so ziemlich jeder Sprache schreiben (abgesehen von einigen esoterischen Sprachen wie Brainfuck oder INTERCAL).

Natürlich bleibt das Problem bestehen, dass die Standard-C-Bibliothek keine dieser raffinierten Containerklassen enthält, an die Sie gewöhnt sind. Leider bedeutet dies, dass Sie entweder Ihre eigenen verwenden müssen oder diesen Mangel mithilfe von dynamisch zugewiesenen Arrays umgehen müssen. Aber ich wette, Sie werden bald feststellen, dass alles, was Sie wirklich brauchen, dynamische Arrays ( malloc()) und verknüpfte Listen / Bäume sind, die über Zeiger-Member in Ihren Klassen implementiert werden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.