Voici un problème de programmation / langage sur lequel j'aimerais avoir votre avis.
Nous avons développé des conventions que la plupart des programmeurs (devraient) suivre qui ne font pas partie de la syntaxe des langages mais servent à rendre le code plus lisible. Ce sont bien sûr toujours un sujet de débat mais il y a au moins quelques concepts de base que la plupart des programmeurs trouvent agréables. Nommer vos variables de manière appropriée, nommer en général, rendre vos lignes pas excessivement longues, éviter les longues fonctions, les encapsulations, ces choses.
Cependant, il y a un problème sur lequel je n'ai encore trouvé personne commentant et qui pourrait bien être le plus important du groupe. C'est le problème des arguments qui sont anonymes lorsque vous appelez une fonction.
Les fonctions proviennent des mathématiques où f (x) a une signification claire car une fonction a une définition beaucoup plus rigoureuse qu'elle ne le fait habituellement en programmation. Les fonctions pures en mathématiques peuvent faire beaucoup moins que ce qu'elles peuvent en programmation et elles sont un outil beaucoup plus élégant, elles ne prennent généralement qu'un seul argument (qui est généralement un nombre) et elles renvoient toujours une valeur (également généralement un nombre). Si une fonction prend plusieurs arguments, ce ne sont presque toujours que des dimensions supplémentaires du domaine de la fonction. En d'autres termes, un argument n'est pas plus important que les autres. Ils sont explicitement ordonnés, bien sûr, mais à part cela, ils n'ont pas d'ordre sémantique.
En programmation, cependant, nous avons plus de fonctions définissant la liberté, et dans ce cas, je dirais que ce n'est pas une bonne chose. Une situation courante, vous avez une fonction définie comme ceci
func DrawRectangleClipped (rectToDraw, fillColor, clippingRect) {}
En regardant la définition, si la fonction est écrite correctement, c'est parfaitement clair quoi. Lors de l'appel de la fonction, vous pourriez même avoir une magie d'intellisense / complétion de code en cours dans votre IDE / éditeur qui vous dira quel devrait être l'argument suivant. Mais attendez. Si j'en ai besoin lorsque j'écris l'appel, n'y a-t-il pas quelque chose qui nous manque ici? La personne qui lit le code n'a pas l'avantage d'un IDE et à moins qu'elle ne passe à la définition, elle ne sait pas lequel des deux rectangles passés comme arguments est utilisé pour quoi.
Le problème va encore plus loin. Si nos arguments proviennent d'une variable locale, il peut y avoir des situations où nous ne savons même pas quel est le deuxième argument car nous ne voyons que le nom de la variable. Prenons par exemple cette ligne de code
DrawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])
Cela est atténué à divers degrés dans différentes langues, mais même dans des langues strictement typées et même si vous nommez vos variables de manière judicieuse, vous ne mentionnez même pas le type de la variable lorsque vous la passez à la fonction.
Comme c'est généralement le cas avec la programmation, il existe de nombreuses solutions potentielles à ce problème. Beaucoup sont déjà implémentés dans les langues populaires. Paramètres nommés en C # par exemple. Cependant, tout ce que je sais présente des inconvénients importants. Nommer chaque paramètre sur chaque appel de fonction ne peut pas conduire à un code lisible. On dirait presque que nous dépassons peut-être les possibilités que nous offre la programmation en texte brut. Nous sommes passés du texte JUST dans presque tous les domaines, mais nous codons toujours le même. Plus d'informations sont nécessaires pour être affichées dans le code? Ajoutez plus de texte. Quoi qu'il en soit, cela devient un peu tangentiel, donc je vais m'arrêter ici.
Une réponse que j'ai obtenue au deuxième extrait de code est que vous décompressez probablement d'abord le tableau dans certaines variables nommées, puis les utilisez, mais le nom de la variable peut signifier beaucoup de choses et la façon dont elle est appelée ne vous indique pas nécessairement la façon dont elle est censée être interprété dans le contexte de la fonction appelée. Dans la portée locale, vous pouvez avoir deux rectangles nommés leftRectangle et rightRectangle car c'est ce qu'ils représentent sémantiquement, mais il n'a pas besoin de s'étendre à ce qu'ils représentent lorsqu'ils sont donnés à une fonction.
En fait, si vos variables sont nommées dans le contexte de la fonction appelée, vous introduisez moins d'informations que vous ne le pourriez potentiellement avec cet appel de fonction et à un certain niveau si cela conduit à un code pire. Si vous avez une procédure qui aboutit à un rectangle que vous stockez dans rectForClipping, puis une autre procédure qui fournit rectForDrawing, alors l'appel réel à DrawRectangleClipped n'est qu'une cérémonie. Une ligne qui ne signifie rien de nouveau et qui est là juste pour que l'ordinateur sache exactement ce que vous voulez, même si vous l'avez déjà expliqué avec votre nom. Ce n'est pas une bonne chose.
J'aimerais vraiment entendre de nouvelles perspectives à ce sujet. Je suis sûr que je ne suis pas le premier à considérer cela comme un problème, alors comment est-il résolu?
Réponses:
Je suis d'accord que la façon dont les fonctions sont souvent utilisées peut être une partie déroutante de l'écriture de code, et en particulier de la lecture de code.
La réponse à ce problème dépend en partie de la langue. Comme vous l'avez mentionné, C # a nommé des paramètres. La solution d'Objective-C à ce problème implique des noms de méthode plus descriptifs. Par exemple,
stringByReplacingOccurrencesOfString:withString:
est une méthode avec des paramètres clairs.Dans Groovy, certaines fonctions prennent des cartes, permettant une syntaxe comme celle-ci:
En général, vous pouvez résoudre ce problème en limitant le nombre de paramètres que vous passez à une fonction. Je pense que 2-3 est une bonne limite. S'il apparaît qu'une fonction a besoin de plus de paramètres, cela me fait repenser le design. Mais cela peut être plus difficile à répondre de manière générale. Parfois, vous essayez d'en faire trop dans une fonction. Parfois, il est logique d'envisager une classe pour stocker vos paramètres. De plus, dans la pratique, je trouve souvent que les fonctions qui prennent un grand nombre de paramètres en ont normalement beaucoup comme optionnelles.
Même dans un langage comme Objective-C, il est logique de limiter le nombre de paramètres. L'une des raisons est que de nombreux paramètres sont facultatifs. Pour un exemple, voir rangeOfString: et ses variations dans NSString .
Un modèle que j'utilise souvent en Java est d'utiliser une classe de style fluide comme paramètre. Par exemple:
Cela utilise une classe comme paramètre et avec une classe de style fluide, le code est facilement lisible.
L'extrait Java ci-dessus aide également lorsque l'ordre des paramètres peut ne pas être aussi évident. Nous supposons normalement avec des coordonnées que X vient avant Y. Et je vois normalement la hauteur avant la largeur comme une convention, mais ce n'est toujours pas très clair (
something.draw(5, 20)
).J'ai également vu certaines fonctions comme,
drawWithHeightAndWidth(5, 20)
mais même celles-ci ne peuvent pas prendre trop de paramètres, ou vous commenceriez à perdre en lisibilité.la source
Dimension(int width, int height)
etGridLayout(int rows, int cols)
(le nombre de lignes est la hauteur, ceGridLayout
qui signifie a la hauteur en premier etDimension
la largeur).array_filter($input, $callback)
versusarray_map($callback, $input)
,strpos($haystack, $needle)
versusarray_search($needle, $haystack)
La plupart du temps, il est résolu par une bonne dénomination des fonctions, des paramètres et des arguments. Vous avez déjà exploré cela et constaté qu'il présentait cependant des lacunes. La plupart de ces lacunes sont atténuées en gardant les fonctions petites, avec un petit nombre de paramètres, à la fois dans le contexte appelant et dans le contexte appelé. Votre exemple particulier est problématique car la fonction que vous appelez essaie de faire plusieurs choses à la fois: spécifier un rectangle de base, spécifier une région de découpage, le dessiner et le remplir avec une couleur spécifique.
C'est un peu comme essayer d'écrire une phrase en utilisant uniquement les adjectifs. Mettez plus de verbes (appels de fonction), créez un sujet (objet) pour votre phrase, et c'est plus facile à lire:
Même si
clipRect
etcolor
ont des noms terribles (et ils ne devraient pas), vous pouvez toujours discerner leurs types à partir du contexte.Votre exemple désérialisé est problématique car le contexte d'appel essaie d'en faire trop à la fois: désérialiser et dessiner quelque chose. Vous devez attribuer des noms qui ont du sens et séparer clairement les deux responsabilités. Au minimum, quelque chose comme ça:
Beaucoup de problèmes de lisibilité sont causés par la tentative d'être trop concis, en sautant les étapes intermédiaires nécessaires aux humains pour discerner le contexte et la sémantique.
la source
En pratique, il est résolu par une meilleure conception. Il est exceptionnellement rare que des fonctions bien écrites prennent plus de 2 entrées, et quand cela se produit, il est rare que ces nombreuses entrées ne puissent pas être agrégées dans un ensemble cohérent. Cela facilite la décomposition des fonctions ou l'agrégation des paramètres, de sorte que vous ne faites pas trop fonctionner une fonction. Une fois qu'il a deux entrées, il devient facile à nommer et beaucoup plus clair sur quelle entrée est laquelle.
Mon langage de jouets avait le concept de phrases pour y faire face, et d'autres langages de programmation plus axés sur le langage naturel ont eu d'autres approches pour y faire face, mais ils ont tous tendance à avoir d'autres inconvénients. De plus, même les phrases ne sont guère plus qu'une belle syntaxe pour que les fonctions aient de meilleurs noms. Il sera toujours difficile de faire un bon nom de fonction quand il faudra un tas d'entrées.
la source
En Javascript (ou ECMAScript ), par exemple, de nombreux programmeurs se sont habitués à
Et comme une pratique de programmation, il est passé des programmeurs à leurs bibliothèques et de là à d'autres programmeurs qui ont commencé à l'aimer et à l'utiliser et à écrire d'autres bibliothèques, etc.
Exemple
Au lieu d'appeler
comme ça:
, qui est un style valide et correct, vous appelez
comme ça:
, ce qui est valide et correct et agréable par rapport à votre question.
Bien sûr, il doit y avoir des conditions appropriées pour cela - en Javascript, c'est beaucoup plus viable que dans, disons, C. En javascript, cela a même donné naissance à une notation structurelle désormais largement utilisée qui est devenue populaire en tant qu'équivalent plus léger de XML. Cela s'appelle JSON (vous en avez peut-être déjà entendu parler).
la source
params
(contenant souvent des arguments facultatifs et souvent lui-même facultatif) comme, par exemple, cette fonction . Cela rend les fonctions avec de nombreux arguments assez faciles à saisir (dans mon exemple, il y a 2 arguments obligatoires et 6 options).Vous devez alors utiliser objective-C, voici une définition de fonction:
Et ici, il est utilisé:
Je pense que ruby a des constructions similaires et vous pouvez les simuler dans d'autres langues avec des listes de valeurs-clés.
Pour les fonctions complexes en Java, j'aime définir des variables factices dans le libellé des fonctions. Pour votre exemple gauche-droite:
Cela ressemble à plus de codage, mais vous pouvez par exemple utiliser leftRectangle puis refactoriser le code plus tard avec "Extraire la variable locale" si vous pensez que cela ne sera pas compréhensible pour un futur responsable du code, qui pourrait ou non être vous.
la source
Mon approche consiste à créer des variables locales temporaires - mais pas seulement à les appeler
LeftRectange
etRightRectangle
. J'utilise plutôt des noms un peu plus longs pour donner plus de sens. J'essaie souvent de différencier les noms autant que possible, par exemple de ne pas les appeler tous les deuxsomething_rectangle
, si leur rôle n'est pas très symétrique.Exemple (C ++):
et je pourrais même écrire une fonction ou un modèle d'emballage à une ligne:
(ignorez les esperluettes si vous ne connaissez pas C ++).
Si la fonction fait plusieurs choses étranges sans bonne description - alors elle doit probablement être refactorisée!
la source