Existe-t-il des directives communément acceptées sur la façon d'écrire du C moderne?

13

J'ai une solide expérience Java / Groovy et j'ai été affecté à une équipe qui maintient une base de code C assez importante pour un logiciel d'administration.

Certains problèmes, comme le traitement des blob dans la base de données ou la génération de rapports au format PDF et Excel, ont été externalisés vers le service Web Java.

Cependant, en tant que développeur Java, je suis un peu confus par certains aspects du code:

  • il est verbeux (surtout lorsqu'il s'agit d'une «exception»)
  • il y a beaucoup de méthodes énormes (beaucoup de méthodes 2000+ lignes)
  • il n'y a pas de structures de données avancées (il me manque beaucoup List, Set et Map)
  • aucune séparation des préoccupations (SQL est joyeusement mélangé tout autour du code)

En conséquence, je sens que l'entreprise est cachée dans des tonnes de code technique et mon cerveau, façonné avec Orienté Objet et une pincée de programmation fonctionnelle, n'est pas à l'aise.

Le bon côté du projet est que le code est simple: il n'y a pas de framework, pas de manipulation de code d'octet à l'exécution, pas d'AOP. Et le serveur peut répondre simultanément à plus de 10000 utilisateurs avec une seule machine en utilisant moins de mémoire que java a besoin pour cracher "hello world".

Je veux apprendre à écrire du code C conformément aux principes modernes généralement acceptés. Y a-t-il des principes communément acceptés sur la façon dont le C moderne devrait être écrit et structuré?

Quelque chose d'un peu comme l'équivalent du livre "Effective Java", mais pour C.

Modifier à la lumière des réponses et des commentaires:

  • Je vais essayer d'adapter mon état d'esprit au code C et ne pas essayer de le refléter dans la POO.
  • J'ai commencé à lire les guides de style de codage recommandés dans le commentaire (Les normes de codage GNU et Le style de codage du noyau Linux).
  • Je vais ensuite essayer de proposer ce style de code à mes collègues. La partie la plus difficile pourrait être de convaincre des collègues qu'une méthode énorme pourrait être divisée en parties plus petites et que répéter les mêmes 4 lignes de code de gestion des erreurs pourrait être évité à l'aide d'une méthode.
Guillaume
la source
5
L'application a-t-elle réellement besoin d'être modernisée, ou pensez-vous simplement que c'est le cas parce que la façon dont elle a été écrite est inconnue?
Blrfl
1
@Blrfl, je pense que l'application a été écrite avec une norme obsolète. Je veux juste savoir quelle est la norme (administrative) actuelle (2016) C. S'il y en a une. Je ne veux pas refactoriser ou remodeler l'application actuelle, je veux avoir une idée de la façon dont dois-je écrire la prochaine partie du code.
Guillaume
3
@antlersoft: Une fonction de 2 000 lignes qui fait une longue liste de choses simples, l'une après l'autre, ne pose absolument aucun problème et n'a pas besoin d'excuse. Veuillez ne pas répondre avec des arguments circulaires comme "vous ne devriez pas écrire 2000 fonctions de ligne parce que vous ne devriez pas écrire 2000 fonctions de ligne".
gnasher729

Réponses:

14

Je peux lire à partir de votre question que le problème n'est pas que le code est du vieux C mais juste une mauvaise programmation. La plupart des problèmes que vous avez mentionnés, comme la verbosité, les énormes fonctions de 2000+ lignes ou l'absence de séparation des préoccupations, sont applicables à n'importe quel langage, C ou Java.

La verbosité a été mentionnée dans le contexte de la gestion des erreurs. Vous n'avez pas fourni d'exemple, je ne peux que rappeler que le code de gestion des erreurs est également du code . Il n'y a aucune excuse pour les sections répétitives du code passe-partout. Factorisez-le; soit vers une fonction ou (si cela ne vaut pas la peine de créer une fonction distincte) faites le goto Error;modèle et déplacez la gestion des erreurs et le nettoyage des ressources vers une Error:section en bas de la fonction.

Si passer l'erreur dans la chaîne d'appels semble être le problème, demandez-vous: la fonction là-haut a-t-elle vraiment besoin de savoir qu'un petit gars ici avait un problème? Les mécanismes d'exception intégrés dans un langage facilitent la tâche, mais en général, il est préférable de gérer les exceptions tôt (dans n'importe quel langage) afin que la condition d'erreur ne pollue pas la logique du code de haut niveau. Et si la fonction là-haut a vraiment besoin de savoir, il existe des moyens d' émuler des exceptions avec setjmpet longjmp.

Je pense que le seul problème vraiment lié à C mentionné est le manque de conteneurs standard. Alors que Setpeut généralement être remplacé par un tableau trié et Map(pour la plupart) par un tableau de paires ou un struct(si vous connaissez le jeu de clés avant, map[key] = valuese transforme en s.key = value), mais le fait est qu'il n'y a pas de conteneur de tableau dynamique dans la norme bibliothèque. En C99, vous pouvez au moins déclarer un tableau de longueur variable sur la pile ( int array[len]) mais vous devez calculer lenau préalable (généralement pas dur) et bien sûr, vous ne pouvez pas le renvoyer en tant qu'objet alloué à la pile. La plupart des projets finissent par écrire leur propre conteneur de tableau dynamique ou en adopter un open-source.

Pour terminer, j'aimerais souligner que j'y suis allé. J'ai été le programmeur Java qui est passé au C ++ et au C. pur. Je voudrais conseiller "lire le livre X pour apprendre le bon C" mais il n'y en a pas comme il n'y en a pas pour Java. La voie à suivre consiste à s'imprégner de toutes les subtilités du langage et de la bibliothèque standard; google beaucoup, lire beaucoup et coder beaucoup jusqu'à ce que vous commenciez à penser en C. Essayer d'écrire des choses en C comme vous le feriez en Java est aussi frustrant que d'essayer d'écrire une phrase dans une langue étrangère avec des mots directement traduits de votre mère langue; vous et le lecteur grincerez des dents. La bonne nouvelle est que l'apprentissage d'une bonne programmation est lent mais l'apprentissage d'une autre langue est rapide. donc si vous écrivez du code décent en Java,

Une chouette
la source
1
Dans l'ensemble, c'est une très bonne réponse. Je voudrais simplement m'opposer à voir setjmp()/ longjmp()comme un outil valide: il n'essaie même pas d'effectuer un nettoyage. Toute allocation sera divulguée, aucun verrou maintenu ne sera libéré, aucun fichier ouvert ne sera fermé et toute incohérence transitoire des données deviendra permanente. À mon humble avis, cette paire de fonctions est fondamentalement le pire hack jamais inventé, avec la seule justification qu'il était possible de le mettre en œuvre. En fin de compte, il n'y a vraiment qu'une seule façon valide de gérer les erreurs dans C: les codes d'erreur explicites.
cmaster - réintègre monica
@cmaster yea. Personnellement, on setjmp/longjmpdirait un poisson hors de l'eau en C et je ne les ai jamais utilisés. Je me sentais obligé de les inclure uniquement en raison des nombreux tutoriels / bibliothèques sur Internet pour imiter les exceptions, alors j'ai pensé qu'il y avait des gens qui l'utilisaient réellement.
Un hibou
7

Le bon côté du projet est que le code est simple: il n'y a pas de framework, pas de manipulation de code d'octet à l'exécution, pas d'AOP. Et le serveur peut répondre simultanément à plus de 10000 utilisateurs avec une seule machine en utilisant moins de mémoire que java a besoin pour cracher "hello world".

Je vous recommanderais d'être prudent quant à savoir si cela vaut votre temps et l'argent de l'entreprise pour dépenser des ressources pour "moderniser" un logiciel qui fonctionne avec une faible complexité de code et qui fonctionne bien. Il y a un risque élevé que vous introduisiez vous-même de nouveaux bogues, d'autant plus qu'il semble s'agir d'un système que vous ne connaissez pas.

Si vous voulez toujours emprunter cette voie, je suggère ce qui suit:

  • Faire (ou générer) un diagramme d'état du logiciel / code
  • Plongez dans le code et faites une liste des parties les plus complexes ou critiques du code respectivement
  • Trouvez quelqu'un qui connaît bien cette base de code et demandez-lui pourquoi il a été construit de cette façon et ce qui a été connu pour poser problème
  • Écrivez la documentation de ce que vous avez appris

À ce stade, vous déciderez si cela vaut la peine d'être exploré. Si la culture de votre entreprise ne récompense pas l'échec, obtenez le feu vert d'un supérieur ou d'un gestionnaire.

  • Compartimenter les différents blocs de construction du logiciel et écrire des tests unitaires pour chacun.
  • Itérer jusqu'à ce que vous puissiez coller les différents modules ensemble
  • Faites des tests supplémentaires qui simulent une interaction réelle de l'utilisateur (tests de résistance, etc.)

Je pense que c'est une bonne feuille de route et vous emmener où vous en avez besoin. Sans connaître les spécificités de ce projet, il est difficile de vous aider beaucoup. Veuillez ne pas rejeter ma clause de non-responsabilité car je suis trop alarmiste. Des tonnes d'excellents programmeurs ont battu la poussière en essayant de réécrire un projet existant dans leur langue préférée ou en utilisant des outils "modernes". C'est une décision qui doit être mûrement réfléchie et je vous exhorte à ne pas vous laisser aller et à le faire vous-même sans le soutien de la direction ou l'aide de vos collègues.

Jerry Dean
la source
2
Je me rends compte que ma question n'était pas claire du tout. Je ne veux pas refactoriser le code. Du tout. Je souhaite conserver la base de code existante telle qu'elle est. Cependant, je veux apprendre à écrire du C moderne pour la nouvelle fonctionnalité. Et là, je suis perdu. La plupart de la documentation que j'ai trouvée porte sur la façon de coder en C, pas sur la façon d'écrire un C. moderne. Peut-être qu'il n'y a rien de tel que le C moderne ...
Guillaume
1

Si vous préférez un langage de niveau supérieur, il existe certains langages comme C ++ ou Objective-C qui peuvent être mélangés avec du code C assez facilement.

Alternativement, C et C ++ sont raisonnablement compatibles. Vous pourrez peut-être simplement compiler la base de code entière en C ++ avec quelques modifications - vous aurez la variable occasionnelle nommée "classe" ou "modèle" que vous devez renommer, mais en pratique, ce sera tout. (sizeof ('a') est différent en C et C ++, mais je ne pense pas l'avoir jamais utilisé).

Si vous optez pour cette voie, considérez que le responsable suivant ne maîtrisera peut-être pas trop C ++. Ne vous laissez pas emporter. Tirez parti de C ++, mais seulement dans la mesure où un programmeur C peut facilement le comprendre.

gnasher729
la source
1
Je dois être en désaccord ici. C et C ++ sont des langages distincts, et du code requis par un compilateur C ++ (transtypant explicitement la valeur de retour de malloc) est considéré comme une mauvaise pratique en C. La signification de constet inlineest également très différente entre C et C ++, et bien sûr C ++ ne comprend pas __restrict. Ne traitez pas les langues comme interchangeables, même dans le sous-ensemble de sources qui se compilent dans les deux.
Angew n'est plus fier de SO
1

Fondamentalement, écrire un bon code C est identique à écrire un bon code C ++ ou Java: vous voulez une classe, utilisez a struct. Vous voulez l'héritage, incluez la base en structtant que premier membre sans nom. Vous voulez des fonctions virtuelles, ajoutez un pointeur à une statique structde pointeurs de fonction. Et ainsi de suite, etc. C'est exactement ce que fait C ++ sous le capot, la seule différence est qu'il est explicite en C. Ainsi, vous pouvez faire une programmation parfaitement orientée objet en C, il semble juste un peu différent et plus chaud que ce que vous être habitué.

Le fait est qu'une bonne programmation concerne les paradigmes, pas les fonctionnalités du langage. Certes, il est toujours agréable que vos fonctionnalités linguistiques fournissent un bon support pour les paradigmes que vous souhaitez utiliser, mais les fonctionnalités linguistiques ne sont pas obligatoires. Une fois que vous vous en rendez compte, vous pouvez écrire du bon code dans à peu près n'importe quel langage (à l'exception de certains langages ésotériques comme brainfuck ou INTERCAL, c'est-à-dire).

Bien sûr, le problème demeure que la bibliothèque C standard ne contient aucune de ces classes de conteneurs astucieuses auxquelles vous êtes habitué. Malheureusement, cela signifie que vous devrez soit utiliser le vôtre, soit contourner ce manque en utilisant des tableaux alloués dynamiquement. Mais je parie que vous découvrirez bientôt que tout ce dont vous avez vraiment besoin est de tableaux dynamiques ( malloc()) et de listes / arborescences liées qui sont implémentées via des membres de pointeur dans vos classes.

cmaster - réintégrer monica
la source