beaucoup personnes ont fait valoir la taille de la fonction. Ils disent que des fonctions en général devraient être assez courtes. Les opinions varient de quelque chose comme 15 lignes à "environ un écran", qui est probablement environ 40-80 lignes.
En outre, les fonctions doivent toujours remplir une tâche uniquement. P>
Cependant, il existe un type de fonction qui échoue fréquemment dans les deux critères de mon code: fonctions d'initialisation. P>
Par exemple dans une application audio, le matériel audio / API doit être configuré, les données audio doivent être converties en format approprié et l'état d'objet doit être correctement initialisé. Ce sont clairement trois tâches différentes et en fonction de l'API, cela peut facilement couvrir plus de 50 lignes. p>
La chose avec les fonctions init est qu'ils ne sont généralement appelés qu'une seule fois, il n'est donc pas nécessaire de réutiliser aucun des composants. Souhaitez-vous toujours les briser dans plusieurs fonctions plus petites pourriez-vous envisager de grandes fonctions d'initialisation pour être OK? P>
9 Réponses :
Si la brise en parties plus petites rend le code mieux structuré et / ou plus lisible - faites-le, quelle que soit la fonction. Il ne s'agit pas du nombre de lignes qu'il s'agit de la qualité de code. P>
Dans une situation comme celle-ci, je pense que cela revient à une question de préférence personnelle. Je préfère avoir des fonctions seulement une chose, alors je diviserais l'initialisation en fonctions distinctes, même si elles ne sont appelées qu'une seule fois. Cependant, si quelqu'un voulait tout faire dans une seule fonction, je ne m'inquiéterais pas trop (tant que le code était clair). Il y a des choses plus importantes à argumenter (comme si les bretelles bouclées appartiennent à leur propre ligne séparée). P>
Si vous avez beaucoup de composants, il est nécessaire d'être branché entre eux, il peut certainement être raisonnablement naturel d'avoir une méthode importante - même si la création de chaque composant est refactable dans une méthode séparée dans laquelle faisait réalisable. P >
Une alternative à cela consiste à utiliser un cadre d'injection de dépendance (par exemple le ressort, le château Windsor, la Guice, etc.). Qui a des avantages et des inconvénients parfaits ... Tout en travaillant à travers une grande méthode peut être assez douloureux, vous avez au moins une bonne idée d'où tout est initialisé, et il n'est pas nécessaire de s'inquiéter de ce que "magie" pourrait se passer . Là encore, l'initialisation ne peut pas être modifiée après le déploiement (comme cela peut être utilisé avec un fichier XML pour le ressort, par exemple). P>
Je pense qu'il est logique de concevoir le corps principal de votre code afin que celui-ci soit injecté em> peut être injecté - mais si cette injection est via un cadre ou juste une liste de codée dur (et potentiellement longue) des appels d'initialisation est un choix qui pourrait bien changer pour différents projets. Dans les deux cas, les résultats sont difficiles à tester autre que en exécutant simplement l'application. P>
Je briserais toujours la fonction par tâche, puis appelle chacune des fonctions de niveau inférieur de ma fonction d'initialisation en face publique: rédaction succincte fonctionne autant d'isoler faute et changement comme gardant les choses lisibles. Si vous savez que l'échec est dans Un point final, je utilisais _convert_format () code>, vous pouvez suivre les ~ 40 lignes responsables d'un virus un peu plus rapidement. La même chose s'applique si vous commettez des modifications qui ne touchent qu'une seule fonction. P> assert () code> assez souvent afin que je puisse "échouer souvent et échouer tôt «Et le début d'une fonction est le meilleur endroit pour quelques affirmations de contrôle de la santé mentale. Garder la fonction courte vous permet de tester la fonction de manière plus approfondie en fonction de son ensemble de tâches plus étroite. Il est très difficile de tester une fonction de 400 lignes qui fait 10 choses différentes. P> p>
+1: "Il n'est pas nécessaire de réutiliser aucun des composants." La réutilisation n'est pas la question. Écrire quelque chose qui peut être compris et maintenu par d'autres personnes est de loin, beaucoup plus important.
Je me souviens d'un avis de départ des identifiants de départ en commençant par des souligneurs à l'usage interne du compilateur C et en les évitant dans des programmes. En outre, vous devez marquer ces trois fonctions init à une fois d'activation en tant que statique code>. Pour une fois, ils ne seront pas utilisés en dehors du fichier source actuel. Et comme avantage supplémentaire, un compilateur intelligent verra alors qu'ils ne sont appelés qu'une seule fois, et juste en ligne le code (juste au cas où vous seriez inquiet à propos de cet appelant).
J'essaierais toujours de briser les fonctions en unités logiques. Ils devraient être aussi longs ou aussi courts que de sens. Par exemple:
SetupAudioHardware(); ConvertAudioData(); SetupState();
Premièrement, une usine doit être utilisée à la place d'une fonction d'initialisation. C'est plutôt que d'avoir Cependant, soyez prudent non plus de résumé trop tôt. Il est clair que vous avez déjà deux préoccupations: 1) l'initialisation audio et 2) en utilisant cet audio. Jusqu'à par exemple, vous résumez le périphérique audio à initialiser, ou la manière dont un périphérique donné peut être configuré lors de l'initialisation, votre méthode d'usine ( Notez que initialisze_audio () code>, vous avez un nouveau audioObjactory code> (vous pouvez penser à un meilleur nom ici). Cela maintient la séparation des préoccupations. P>
audioObjactory.create () code> ou autre), devrait vraiment être conservé à une seule grande méthode. L'abstraction précoce ne sert que d'obscurcir la conception. P>
audioObjactory.create () code> n'est pas quelque chose qui peut être testé unitaire. Testant C'est un test d'intégration et jusqu'à ce qu'il y ait des parties qui peuvent être résumées, il restera un test d'intégration. Plus tard, vous constaterez peut-être que vous avez plusieurs usines différentes pour différentes configurations; À ce stade, il pourrait être bénéfique pour abstraiter les appels matériels dans une interface, vous pouvez donc créer des tests d'unités pour vous assurer que les différentes usines configurent le matériel de manière appropriée. P>
En fait, c'est à peu près ce que je fais. La classe elle-même est censée être un objet de joueur audio qui résume la manipulation du gâchis qui est Coreaidio. Vraiment, la préparation audio de données et l'initialisation matérielle audio sont très bien connectées car différentes données audio exigent une configuration différente et inversement.
Je pense que c'est la mauvaise approche d'essayer de compter le nombre de lignes et de déterminer les fonctions en fonction de cela. Pour quelque chose comme le code d'initialisation, j'ai souvent une fonction distincte pour cela, mais principalement pour que la charge ou l'init ou la nouvelle fonction ne soit pas encombrée et déroutante. Si vous pouvez le séparer en quelques tâches telles que d'autres, vous pouvez le nommer quelque chose d'utile et vous aider à organiser. Même si vous l'appelez juste une fois, ce n'est pas une mauvaise habitude, et vous constatez souvent qu'il y a des moments où vous voudrez peut-être réinitier des choses et pouvez utiliser cette fonction à nouveau. P>
Je pensais juste que je jetais ça là-bas, car il n'a pas encore été mentionné - le façade Le modèle est parfois cité comme une interface avec un sous-système complexe. Je n'ai pas fait beaucoup avec elle moi-même, mais les métaphores sont généralement quelque chose comme allumer un ordinateur (nécessite plusieurs étapes) ou allumer un système de cinéma à domicile (allumez le téléviseur, allumez le récepteur, refuser les lumières, etc. .) p>
Selon la structure de code, vous pourriez être quelque chose qui vaut la peine d'être considéré comme abstrait de vos fonctions d'initialisation importantes. Je suis toujours d'accord avec le point de MEAGAR, cependant que cette réduction des fonctions dans _Init_x (), _init_y () code>, etc. est un bon moyen d'y aller. Même si vous n'allez pas réutiliser des commentaires dans ce code, lors de votre prochain projet, lorsque vous vous dites: «Comment ai-je initialisé ce composant X?», Ce sera beaucoup plus facile de revenir en arrière et de le ramener de la fonction inférieure _init_x () code> fonctionner qu'il ne serait de le choisir d'une fonction plus grande, surtout si l'initialisation X est dispersée dans l'ensemble. P>
La longueur de la fonction est, comme vous avez marqué, une affaire très subjective. Cependant, une meilleure pratique standard consiste à isoler le code souvent répété et / ou peut fonctionner comme sa propre entité. Par exemple, si votre fonction d'initialisation est chargée des fichiers de bibliothèque ou des objets qui seront utilisés par une bibliothèque spécifique, ce bloc de code doit être moduleux. P>
Avec cela dit, il n'est pas mauvais d'avoir une méthode d'initialisation tant longue, tant qu'elle n'est pas longue à cause de beaucoup de code répété ou d'autres extraits pouvant être abstraits. P>
espère que cela aide,
Carlos Nunez P>