Le code est une pensée. Une tâche apparaît et le développeur pense comment la résoudre, comment exprimer les exigences dans les fonctions et les classes, comment se faire des amis, comment atteindre la rigueur et l'exactitude, et comment faire fonctionner l'entreprise correctement. Pratiques, techniques, principes, modèles et approches - tout doit être pris en compte et tout doit être rappelé.
Et parallèlement à cela, nous voyons une épidémie généralisée de gestionnaires, aides, services, contrôleurs, sélecteurs, adaptateurs, getters, setters et autres mauvais esprits: tout cela est du code mort. Il encombre et encombre.
Je propose de lutter de cette façon: vous devez présenter les programmes sous forme de texte dans un langage naturel et les évaluer en conséquence. Comment cela et ce qui se passe - dans l'article.
Table des matières du cycle
- Les objets
- Actions et propriétés
- Code comme texte
Prologue
Mon expérience est modeste (environ quatre ans), mais plus je travaille, plus je comprends: si le programme est illisible, cela n'a aucun sens. Il est connu depuis longtemps et battu pour le rappeler - le code résout non seulement certains problèmes maintenant , mais aussi plus tard : il est pris en charge, étendu, corrigé. De plus, il a toujours: lu.
Après tout, c'est du texte.
L'esthétique du code en tant que texte est un thème clé du cycle. L'esthétique ici est un verre à travers lequel on regarde les choses et on dit, oui, c'est bien, oui, c'est beau.
En matière de beauté et de compréhensibilité, les mots sont d'une grande importance. Dire: "En ce moment, mes perceptions sont dans un état terne à cause du niveau élevé d'éthanol dans le sang" n'est pas du tout la même chose que: "Je me suis saoulé . "
Nous sommes chanceux, les programmes sont presque entièrement composés de mots.
Disons que vous devez faire «un personnage qui a de la santé et du mana, il marche, attaque, utilise des sorts», et vous pouvez immédiatement voir: il y a des objets (personnage, santé, mana, sort), des actions (marcher, attaquer, utiliser) et des propriétés ( le personnage a la santé, le mana, la vitesse de lancer des sorts) - tout cela sera des noms: classes, fonctions, méthodes, variables, propriétés et champs, en un mot, tout ce que le langage de programmation se divise.
Mais je ne distinguerai pas les classes des structures, les champs des propriétés et les méthodes des fonctions: un personnage dans le cadre d'un récit ne dépend pas de détails techniques (qu'il peut être représenté soit comme une référence soit comme un type significatif). Une chose essentiellement différente: qu'il s'agit d'un personnage et qu'ils l'appelaient Hero
(ou Character
), et non HeroData
ou HeroUtils
.
Je vais maintenant prendre le verre même de l'esthétique et montrer comment du code est écrit aujourd'hui et pourquoi il est loin d'être parfait.
Les objets
En C # (et pas seulement), les objets - des instances de classes qui sont placées sur le tas, y vivent pendant un certain temps, puis le garbage collector les supprime. Il peut également être créé des structures sur la pile ou des tableaux associatifs, ou autre chose. Pour nous, ce sont: les noms de classe, les noms.
Les noms dans le code, comme les noms en général, peuvent prêter à confusion. Oui, et vous voyez rarement un nom laid, mais un bel objet. Surtout s'il s'agit d'un Manager
.
Manager au lieu d'un objet
UserService
, UserService
, DamageUtils
, MathHelper
, GraphicsManager
, GameManager
, VectorUtil
.
Ce n'est pas l'exactitude et la tangibilité qui dominent ici, mais quelque chose de vague, laissant quelque part dans le brouillard. Pour de tels noms, beaucoup est permis.
Par exemple, dans n'importe quel GameManager
vous pouvez ajouter tout ce qui concerne le jeu et la logique du jeu . Six mois plus tard, viendra une singularité technologique.
Ou, cela arrive, vous devez travailler avec Facebook. Pourquoi ne pas mettre tout le code au même endroit: FacebookManager
ou FacebookService
? Cela semble simple et séduisant, mais une intention aussi vague crée une décision tout aussi vague. Dans le même temps, nous savons qu'il y a des utilisateurs, des amis, des messages, des groupes, de la musique, des intérêts, etc. sur Facebook. Assez de mots!
Non seulement il y a suffisamment de mots: nous les utilisons toujours. Seulement dans le discours ordinaire, pas parmi les programmes.
Et ce n'est pas GitUtils
, mais IRepository
, ICommit
, IBranch
; pas ExcelHelper
, mais ExcelDocument
, ExcelSheet
; pas GoogleDocsService
, mais GoogleDocs
.
Chaque sujet est rempli d'objets. «Les objets étaient marqués par des vides énormes» , «Le cœur battait furieusement» , «La maison était debout» - les objets agissent, ils le sentent, ils sont faciles à imaginer; ils sont quelque part ici, tangibles et denses.
En même temps, vous voyez parfois ceci: dans le référentiel Microsoft/calculator
- CalculatorManager
avec les méthodes: SetPrimaryDisplay
, MaxDigitsReached
, SetParentDisplayText
, OnHistoryItemAdded
...
(Pourtant, je me souviens avoir vu UtilsManager
...)
Cela se produit de cette façon: je veux développer le type List<>
avec un nouveau comportement, et ListUtils
ou ListHelper
sont nés. Dans ce cas, il est préférable et plus précis d'utiliser uniquement des méthodes d'extension - ListExtensions
: elles font partie du concept, et non un vidage des procédures.
L'une des rares exceptions est OfficeManager
tant que publication.
Pour le reste ... Les programmes ne devraient pas être compilés s'ils contiennent de tels mots.
Action au lieu d'objet
IProcessor
, ILoader
, ISelector
, IFilter
, IProvider
, ISetter
, ICreator
, IOpener
, IHandler
; IEnableable
, IInitializable
, IUpdatable
, ICloneable
, IDrawable
, ILoadable
, IOpenable
, ISettable
, IConvertible
.
Ici, l'essence de l'essence est une procédure, pas un concept, et le code perd à nouveau son imagerie et sa lisibilité, et le mot habituel est remplacé par un mot artificiel.
ISequence
est beaucoup plus ISequence
, pas IEnumerable
; IBlueprint
, pas ICreator
; IButton
, pas IButtonPainter
; IPredicate
, pas IFilter
; IGate
, non IOpeneable
; IToggle
, pas IEnableable
.
Une bonne histoire raconte les personnages et leur évolution, et non la façon dont le créateur crée, le constructeur construit et le peintre dessine. Une action ne peut pas représenter complètement un objet. ListSorter
n'est pas une SortedList
.
Prenez DirectoryCleaner
, par exemple, un objet qui nettoie les dossiers du système de fichiers. Est-ce élégant? Mais nous ne disons jamais: "Demandez au nettoyeur de dossier de nettoyer D: / Test" , toujours: "Nettoyez D: / Test" , de sorte que le Directory
avec la méthode Clean
semble plus naturel et plus proche.
Un cas plus vivant est plus intéressant: FileSystemWatcher
de .NET est un observateur de système de fichiers qui rapporte les changements. Mais pourquoi tout l'observateur, si les changements eux - mêmes peuvent signaler qu'ils se sont produits? De plus, ils doivent être inextricablement liés au fichier ou au dossier, ils doivent donc également être placés dans le Directory
ou le File
(en utilisant la propriété Changes
avec la possibilité d'appeler file.Changes.OnNext(action)
).
De tels noms verbaux semblent justifier le modèle de conception de la Strategy
, demandant «d'encapsuler la famille d'algorithmes» . Mais si au lieu d'une «famille d'algorithmes» nous trouvons un objet authentique qui existe dans l'histoire, nous verrons que la stratégie n'est qu'une généralisation.
Pour expliquer ces erreurs et bien d'autres, nous nous tournons vers la philosophie.
L'existence précède l'essence
MethodInfo
, ItemData
, AttackOutput
, CreationStrategy
, StringBuilder
, SomethingWrapper
, LogBehaviour
.
De tels noms sont unis par une chose: leur être est basé sur des détails.
Il arrive que quelque chose interfère rapidement avec une tâche: quelque chose manque ou est, mais pas quelque chose. Ensuite, vous pensez: «Une chose qui peut faire X maintenant m'aiderait» - c'est ainsi que l' existence est conçue. Ensuite, pour «faire» X, XImpl
est écrit - c'est ainsi que l' entité apparaît.
Par conséquent, au lieu de IArrayItem
, IIndexedItem
ou IItemWithIndex
est plus courant, ou, disons, dans l'API Reflection, au lieu de la méthode ( Method
), nous ne voyons que des informations à ce sujet ( MethodInfo
).
Une manière plus vraie: face au besoin d'existence, trouver une entité qui le met en œuvre, et, puisque c'est sa nature, d'autres.
Par exemple, ils voulaient changer la valeur du type string
sans créer d'instances intermédiaires - cela s'est avéré être une solution simple sous la forme de StringBuilder
, alors qu'à mon avis, il est plus approprié - MutableString
.
Rappelez le système de fichiers: DirectoryRenamer
pas nécessaire pour renommer les dossiers, car dès que vous acceptez la présence de l'objet Directory
, l'action y est déjà , vous n'avez tout simplement pas trouvé la méthode correspondante dans le code.
Si vous voulez décrire comment prendre un verrou , il n'est pas nécessaire de préciser qu'il s'agit de ILockBehaviour
ou ILockStrategy
, ce qui est beaucoup plus facile - ILock
(avec la méthode Acquire
qui retourne IDisposable
) ou ICriticalSection
(avec Enter
).
Cela inclut également toutes sortes de Data
, d' Info
, de Output
, d' Input
, d' Args
, de Params
(moins souvent l' State
) - des objets qui sont complètement dépourvus de comportement, car ils étaient considérés comme unilatéraux.
Lorsque l'existence est principale, le quotient est mélangé avec le général et les noms des objets prêtent à confusion - vous devez lire chaque ligne et comprendre où le personnage est allé et pourquoi il n'y a que ses Data
.
Taxonomie bizarre
CalculatorImpl
, AbstractHero
, ConcreteThing
, CharacterBase
.
Pour les mêmes raisons que celles décrites ci-dessus, nous voyons parfois des objets pour lesquels une place dans la hiérarchie est précisément indiquée. Encore une fois, l'existence se précipite, nous voyons à nouveau comment le besoin immédiat a été précipité dans le code, sans tenir compte des conséquences.
Après tout, y a-t-il vraiment une personne ( Human
) - l'héritière de la personne de base ( HumanBase
)? Mais comment se fait-il que Item
hérite de AbstractItem
?
Parfois, ils veulent montrer qu'il n'y a pas de Character
, mais une sorte de ressemblance "brute" - CharacterRaw
.
Impl
, Abstract
, Custom
, Base
, Concrete
, Internal
, Raw
- un signe d'instabilité, d'imprécision de l'architecture qui, comme le pistolet de la première scène, tirera certainement plus tard.
Répétitions
Avec les types imbriqués, cela se produit: RepositoryItem
dans Repository
, WindowState
dans Window
, HeroBuilder
dans Hero
.
La répétition coupe le sens, exacerbe les défauts et ne contribue qu'à la complexité du texte.
Pièces redondantes
Pour synchroniser les threads, ManualResetEvent
est souvent utilisé avec l'API suivante:
public class ManualResetEvent {
Chaque fois, personnellement, je dois me rappeler en quoi Set
diffère de Reset
(grammaire inconfortable) et ce qu'est un «événement de réinitialisation manuelle» en général dans le contexte du travail avec des flux.
Dans de tels cas, il est plus facile d'utiliser des métaphores loin de la programmation (mais proche de la vie quotidienne):
public class ThreadGate { void Open(); void Close(); bool WaitForOpen(); }
Il n'y a certainement rien à confondre!
Parfois, il s'agit du ridicule: spécifiez que les éléments ne sont pas seulement des Items
, mais nécessairement des éléments ItemsList
ou ItemsDictionary
!
Cependant, si la ItemsList
pas drôle, alors AbstractInterceptorDrivenBeanDefinitionDecorator
de Spring est très ItemsList
. Les mots de ce nom sont des chiffons à partir desquels un monstre gigantesque est cousu. Bien que ... S'il s'agit d'un monstre, qu'est-ce que HasThisTypePatternTriedToSneakInSomeGenericOrParameterizedTypePatternMatchingStuffAnywhereVisitor
? Espérons l' héritage .
En plus des noms de classe et d'interface, on rencontre souvent une redondance dans les variables ou les champs.
Par exemple, un champ de type IOrdersRepository
est appelé _ordersRepository
. Mais dans quelle mesure est-il important de signaler que les commandes sont soumises par le référentiel? Après tout, beaucoup plus facile - _orders
.
Il arrive également que dans les requêtes LINQ, ils écrivent les noms d'argument complets des expressions lambda, par exemple, Player.Items.Where(item => item.IsWeapon)
, bien que nous comprenions déjà cet élément en regardant Player.Items
. Dans de tels cas, j'aime toujours utiliser le même caractère - x
: Player.Items.Where(x => x.IsWeapon)
(avec la continuation à y
, z
si ce sont des fonctions à l'intérieur des fonctions).
Total
J'avoue qu'avec un tel début, il ne sera pas facile de trouver la vérité objective. Quelqu'un, par exemple, dira: écrire Service
ou ne pas écrire est un point discutable, insignifiant, de bon goût, et quelle différence cela fait-il si cela fonctionne?
Mais vous pouvez boire dans des gobelets jetables!
Je suis convaincu que le chemin vers le contenu passe par la forme, et si l'on ne regarde pas la pensée, elle semble avoir disparu. Dans le texte du programme, tout fonctionne de la même manière: le style, l'atmosphère et le rythme aident à s'exprimer non confus, mais compréhensible et capacitif.
Le nom de l'objet n'est pas seulement son visage, mais aussi l'être, soi. Il détermine s'il sera éthéré ou saturé, abstrait ou réel, sec ou animé. Le nom change - le contenu change.
Dans le prochain article, nous parlerons de ce contenu et de ce qu'il se passe.