The code is a thought. A task appears, and the developer thinks how to solve it, how to express requirements in functions and classes, how to make friends with them, how to achieve rigor and correctness and how to get a business done. Practices, techniques, principles, patterns and approaches - everything must be taken into account and everything must be remembered.
And at the same time we see a widespread epidemic of managers, helpers, services, controllers, selectors, adapters, getters, setters and other evil spirits: all this is dead code. It binds and clutters.
I propose to fight this way: you need to present programs as a text in natural language and evaluate them accordingly. How it is and what happens - in the article.
My experience is modest (about four years), but the more I work, the more I understand: if the program is unreadable, there is no sense from it. It has long been known and beaten to remind - the code not only solves some problem now , but also then : it is supported, expanded, ruled. At the same time, it is always: read.
After all, this is a text.
Aesthetics of code as text is the key theme of the cycle. Aesthetics here is a window through which we look at things and say, yes, it is good, yes, it is beautiful.
In matters of beauty and clarity, words are of great importance. To say: "At the moment, my perceptions are in a state of dullness due to the high level of ethanol in the blood" is not the same as: "I got drunk . "
We are lucky, the programs are almost entirely composed of words.
Let's say you need to make a “character who has health and mana, he walks, attacks, uses spells” , and it’s immediately obvious: there are objects (character, health, mana, spell), actions (walk, attack, use) and properties ( a character has health, mana, spell casting speed) - all these will be names: classes, functions, methods, variables, properties and fields, in short, everything that a programming language falls into.
But I will not distinguish classes from structures, fields from properties and methods from functions: a character as part of the narration does not depend on technical details (that it can be represented either by reference or by a significant type). Another thing is significant: what is a character and what was called it Hero
(or Character
), not HeroData
or HeroUtils
.
Now I’ll take that aesthetic window and show how some code is written today and why it is far from perfect.
In C # (and not only), objects are instances of classes that reside on the heap, live there for a while, and then the garbage collector deletes them. It can also be created structures on the stack or associative arrays, or something else. For us, they are: class names, nouns.
Names in the code, as well as names in general, can be confusing. Yes, and rarely meet ugly name, but a beautiful object. Especially if it is a Manager
.
UserService
, AccountManager
, DamageUtils
, MathHelper
, GraphicsManager
, GameManager
, VectorUtil
.
It is not accuracy and tangibility that prevails, but something vague, leaving somewhere in the fog. For such names, much is permissible.
For example, in any GameManager
you can add anything that relates to the game and game logic. In six months there will come a technological singularity.
Or, it happens, you need to work with Facebook. Why not put all the code in one place: FacebookManager
or FacebookService
? It sounds seductively simple, but such a vague intention gives rise to an equally vague solution . At the same time, we know that there are users, friends, messages, groups, music, interests, etc. on Facebook. There are enough words!
Not only words suffice: we still use them. Only in ordinary speech, and not among the programs.
And after all not GitUtils
, but IRepository
, ICommit
, IBranch
; not ExcelHelper
, but ExcelDocument
, ExcelSheet
; not GoogleDocsService
, but GoogleDocs
.
Every subject area is filled with objects. “Objects were marked by huge voids” , “My heart was pounding wildly” , “The house was standing” - objects act, feel, they are easy to imagine; they are somewhere here, tangible and dense.
At the same time, you sometimes see this: in the Microsoft/calculator
repository - CalculatorManager
with the methods: SetPrimaryDisplay
, MaxDigitsReached
, SetParentDisplayText
, OnHistoryItemAdded
...
(I also remember how I saw UtilsManager
...)
It also happens this way: I want to extend the List<>
type with new behavior, and ListUtils
or ListHelper
are born. In this case, it is better and more accurate to use only extension methods - ListExtensions
: they are part of the concept, and not a dump of procedures.
One of the few exceptions is OfficeManager
as a position.
Otherwise ... Programs should not be compiled if they contain such words.
IProcessor
, ILoader
, ISelector
, IFilter
, IProvider
, ISetter
, ICreator
, IOpener
, IHandler
; IEnableable
, IInitializable
, IUpdatable
, ICloneable
, IDrawable
, ILoadable
, IOpenable
, ISettable
, IConvertible
.
Here, the essence of the essence is a procedure, not a concept, and the code again loses its figurativeness and readability, and the word habitual is replaced with an artificial one.
Where ISequence
sounds ISequence
lively than IEnumerable
; IBlueprint
, not ICreator
; IButton
, not IButtonPainter
; IPredicate
, not IFilter
; IGate
, not IOpeneable
; IToggle
, not IEnableable
.
A good story tells about the characters and their development, and not about how the creator creates, the builder builds and the painter draws. An action cannot fully represent an object. ListSorter
is not a SortedList
.
Take, for example, DirectoryCleaner
- an object that cleans up folders in the file system. Is it elegant? But we never say: “Ask the folder cleaner to clean D: / Test” , always: “Clean D: / Test” , therefore the Directory
with the Clean
method looks more natural and closer.
More interesting is a more lively case: .NET FileSystemWatcher
is a file system watcher reporting changes. But why a whole observer, if the changes themselves can report that they happened? Moreover, they should be inextricably linked with a file or folder, so they should also be placed in Directory
or File
(with the Changes
property with the ability to call file.Changes.OnNext(action)
).
Such verbal names seem to justify the Strategy
design pattern, which prescribes “encapsulate a family of algorithms” . But if instead of the “family of algorithms” we find an object that is genuine, existing in the narration, we will see that the strategy is just a generalization.
To explain these and many other errors, we turn to philosophy.
MethodInfo
, ItemData
, AttackOutput
, CreationStrategy
, StringBuilder
, SomethingWrapper
, LogBehaviour
.
Such names are united by one thing: their being is based on particulars.
Sometimes it happens that something quickly interferes with solving a problem: there is something or there is one, but not that. Then you think: "The thing that can do X would help me now" - this is how existence is thought. Then XImpl
is written to "do" X - so the entity appears.
Therefore, instead of IArrayItem
, IIndexedItem
or IItemWithIndex
is more common, or, say, in the Reflection API instead of the method ( Method
), we see only information about it ( MethodInfo
).
A surer way: faced with the necessity of existence, find an entity that implements it, and, since such is its nature, others.
For example, you wanted to change the value of the string
type without creating intermediate instances - it turned out to be a straightforward solution in the form of a StringBuilder
, whereas, in my opinion, it would be more appropriate - MutableString
.
Recall the file system: DirectoryRenamer
not needed to rename folders, because once you agree with the presence of the Directory
object, the action is already in it, just in the code you have not yet found the corresponding method.
If you want to describe the method of taking a lock , then it is not necessary to specify that this is ILockBehaviour
or ILockStrategy
, where it is much simpler - ILock
(with the Acquire
method returning IDisposable
) or ICriticalSection
(with Enter
).
Here, all sorts of Data
, Info
, Output
, Input
, Args
, Params
(less commonly State
) are objects that are completely devoid of behavior, because they were considered one-sidedly.
Where existence is primary, there the private is mixed with the general, and the names of the objects are confusing - you have to read into each line and figure out where the character went and why only his Data
.
CalculatorImpl
, AbstractHero
, ConcreteThing
, CharacterBase
.
All for the same reasons described above, we sometimes see objects for which the place in the hierarchy is precisely indicated. Once again, existence is rushing ahead, again we see how the immediate need hastily splashed into the code, without considering the consequences.
After all, is there really a person ( Human
) - the heir to a basic person ( HumanBase
)? And how is it when Item
inherits AbstractItem
?
It happens, they want to show that there is not Character
, but some kind of “raw” likeness - CharacterRaw
.
Impl
, Abstract
, Custom
, Base
, Concrete
, Internal
, Raw
- a sign of instability, vagueness of architecture, which, like the gun from the first scene, will definitely shoot later.
With nested types it happens like this: RepositoryItem
- in the Repository
, WindowState
- in the Window
, HeroBuilder
- in the Hero
.
Repetitions cut the meaning, exacerbate the shortcomings and only contribute to the over-complexity of the text.
ManualResetEvent
with the following API is often used to synchronize threads:
public class ManualResetEvent { // — `EventWaitHandle`. void Set(); void Reset(); bool WaitOne(); }
Personally, I have to remember every time what is the difference between Set
and Reset
(inconvenient grammar) and what is generally a "manually resetting event" in the context of working with threads.
In such cases, it is easier to use metaphors that are far from programming (but close to everyday life):
public class ThreadGate { void Open(); void Close(); bool WaitForOpen(); }
There is certainly nothing to confuse!
Sometimes it comes to the ridiculous: they clarify that objects are not just Items
, but necessarily ItemsList
or ItemsDictionary
!
However, if ItemsList
not funny, then AbstractInterceptorDrivenBeanDefinitionDecorator
from Spring is quite. The words in this name are patches, of which a gigantic monster is sewn. Although ... If this is a monster, then what - HasThisTypePatternTriedToSneakInSomeGenericOrParameterizedTypePatternMatchingStuffAnywhereVisitor
? I hope legacy .
In addition to the names of classes and interfaces, you often see redundancy in variables or fields.
For example, a field of type IOrdersRepository
is called _ordersRepository
. But how important is it to report that orders are submitted by the repository? After all, much easier - _orders
.
It also happens that LINQ queries write the full names of the arguments of lambda expressions, for example, Player.Items.Where(item => item.IsWeapon)
, although we already understand this object ( item
) by looking at Player.Items
. In such cases, I always like to use the same symbol - x
: Player.Items.Where(x => x.IsWeapon)
(with a continuation in y
, z
if these are functions inside functions).
I confess that with such a beginning it will not be easy to find the objective truth. Someone, for example, will say: write Service
or not write - a moot point, irrelevant, taste, and what's the difference if it works?
But also from disposable cups you can drink!
I am convinced: the path to the content lies through the form, and if you do not look at the idea, it is not there. In the text of the program, everything works in the same way: style, atmosphere and rhythm help to express not confused, but understandable and capacious.
The name of an object is not only its face, but also being, the self. It determines whether it will be disembodied or saturated, abstract or real, dry or vivid. The name changes - the content changes.
In the next article we will talk about this very content and how it happens.
Source: https://habr.com/ru/post/447404/
All Articles