Der Code ist lebendig und tot. Teil Eins Die Objekte

Code ist ein Gedanke. Ein Problem tritt auf, und der Entwickler überlegt, wie er es lösen kann, wie er die Anforderungen in Funktionen und Klassen ausdrückt, wie er sie zu Freunden macht, wie er Genauigkeit und Korrektheit erreicht und wie er ein Unternehmen rächt. Praktiken, Techniken, Prinzipien, Muster und Ansätze - alles muss berücksichtigt und alles muss in Erinnerung bleiben.


Und zusammen damit sehen wir eine weit verbreitete Epidemie von Managern, Helfern, Diensten, Controllern, Selektoren, Adaptern, Gettern, Setzern und anderen bösen Geistern: all dies ist toter Code. Er fesselt und fesselt.


Ich schlage vor, auf diese Weise zu kämpfen: Sie müssen Programme als Text in einer natürlichen Sprache präsentieren und entsprechend bewerten. Wie das und was passiert - im Artikel.


Inhaltsverzeichnis durchlaufen


  1. Die Objekte
  2. Aktionen und Eigenschaften
  3. Code als Text

Prolog


Meine Erfahrung ist bescheiden (ungefähr vier Jahre), aber je mehr ich arbeite, desto mehr verstehe ich: Wenn das Programm nicht lesbar ist, macht es keinen Sinn. Es ist seit langem bekannt und geschlagen, um daran zu erinnern - der Code löst nicht nur jetzt , sondern auch später ein Problem: Er wird unterstützt, erweitert, korrigiert. Außerdem liest er immer:


Immerhin ist das Text.


Die Ästhetik von Code als Text ist ein zentrales Thema des Zyklus. Die Ästhetik hier ist ein Glas, durch das wir die Dinge betrachten und sagen: Ja, es ist gut, ja, es ist schön.


In Sachen Schönheit und Verständlichkeit sind Worte von großer Bedeutung. Zu sagen: "Im Moment sind meine Wahrnehmungen aufgrund des hohen Ethanolspiegels im Blut langweilig" ist keineswegs dasselbe wie: "Ich habe mich betrunken . "


Wir haben Glück, dass Programme fast ausschließlich aus Wörtern bestehen.


Nehmen wir an, Sie müssen „einen Charakter machen, der Gesundheit und Mana hat, der geht, angreift, Zauber benutzt“, und Sie können sofort sehen: Es gibt Objekte (Charakter, Gesundheit, Mana, Zauber), Aktionen (Gehen, Angreifen, Verwenden) und Eigenschaften ( Der Charakter hat Gesundheit, Mana, Geschwindigkeit, mit der Zaubersprüche gewirkt werden - all dies sind Namen: Klassen, Funktionen, Methoden, Variablen, Eigenschaften und Felder, in einem Wort, alles, in das sich die Programmiersprache aufteilt.


Aber ich werde Klassen nicht von Strukturen, Felder von Eigenschaften und Methoden von Funktionen unterscheiden: Ein Charakter als Teil einer Erzählung hängt nicht von technischen Details ab (dass er entweder als Referenz oder als signifikanter Typ dargestellt werden kann). Eine wesentlich andere Sache: dass dies ein Charakter ist und dass sie ihn Hero (oder Character ) nannten und nicht HeroData oder HeroUtils .


Jetzt nehme ich das Glas der Ästhetik und zeige, wie Code heute geschrieben wird und warum er alles andere als perfekt ist.


Die Objekte


In C # (und nicht nur) leben Objekte - Instanzen von Klassen, die auf dem Heap platziert sind, eine Weile dort, und dann entfernt der Garbage Collector sie. Es können auch Strukturen auf dem Stapel oder assoziative Arrays oder etwas anderes erstellt werden. Für uns sind sie: Klassennamen, Substantive.


Namen im Code können wie Namen im Allgemeinen verwirrend sein. Ja, und Sie sehen selten einen hässlichen Namen, aber ein schönes Objekt. Besonders wenn es ein Manager .


Manager anstelle eines Objekts


UserService , AccountManager , DamageUtils , MathHelper , GraphicsManager , GameManager , VectorUtil .


Hier dominiert nicht Genauigkeit und Greifbarkeit, sondern etwas Unbestimmtes, das irgendwo im Nebel zurückbleibt. Für solche Namen ist viel zulässig.


In jedem GameManager können Sie beispielsweise alles hinzufügen, was sich auf das Spiel und die Spielelogik bezieht. Sechs Monate später wird es eine technologische Singularität geben.


Oder Sie müssen mit Facebook arbeiten. Warum nicht den gesamten Code an einem Ort FacebookManager : FacebookManager oder FacebookService ? Es klingt verführerisch einfach, aber eine so vage Absicht schafft eine ebenso vage Entscheidung . Gleichzeitig wissen wir, dass es auf Facebook Benutzer, Freunde, Nachrichten, Gruppen, Musik, Interessen usw. gibt. Genug Worte!


Es gibt nicht nur genug Wörter, wir verwenden sie immer noch. Nur in gewöhnlicher Sprache, nicht unter Programmen.


Und es ist nicht GitUtils , sondern IRepository , ICommit , IBranch ; nicht ExcelHelper , sondern ExcelDocument , ExcelSheet ; nicht GoogleDocsService , sondern GoogleDocs .


Jeder Themenbereich ist mit Objekten gefüllt. "Die Objekte waren durch riesige Hohlräume gekennzeichnet" , "Das Herz schlug heftig" , "Das Haus stand" - die Objekte handeln, sie fühlen, sie sind leicht vorstellbar; Sie sind irgendwo hier, greifbar und dicht.


Gleichzeitig wird dies manchmal SetPrimaryDisplay : Im Microsoft/calculator Repository - CalculatorManager mit den folgenden Methoden: SetPrimaryDisplay , MaxDigitsReached , SetParentDisplayText , OnHistoryItemAdded ...


(Trotzdem erinnere ich mich, dass ich UtilsManager einmal gesehen UtilsManager ...)


Dies geschieht folgendermaßen: Ich möchte den Typ List<> mit neuem Verhalten erweitern, und ListUtils oder ListHelper werden geboren. In diesem Fall ist es besser und genauer, nur Erweiterungsmethoden zu verwenden - ListExtensions : Sie sind Teil des Konzepts und kein ListExtensions aus den Prozeduren.


Eine der wenigen Ausnahmen ist OfficeManager als Beitrag.


Im Übrigen ... Programme sollten nicht kompiliert werden, wenn sie solche Wörter enthalten.


Aktion statt Objekt


IProcessor , ILoader , ISelector , IFilter , IProvider , ISetter , ICreator , IOpener , IHandler ; IEnableable , IInitializable , IUpdatable , ICloneable , IDrawable , ILoadable , IOpenable , ISettable , IConvertible .


Hier ist die Essenz der Essenz eine Prozedur, kein Konzept, und der Code verliert wieder seine Bildsprache und Lesbarkeit, und das übliche Wort wird durch ein künstliches ersetzt.


Viel lebendiger ist ISequence , nicht IEnumerable ; IBlueprint , nicht ICreator ; IButton , nicht IButtonPainter ; IPredicate , nicht IFilter ; IGate , nicht IOpeneable ; IToggle , nicht IEnableable .


Eine gute Geschichte erzählt von den Charakteren und ihrer Entwicklung und nicht davon, wie der Schöpfer schafft, der Erbauer baut und der Maler zeichnet. Eine Aktion kann ein Objekt nicht vollständig darstellen. ListSorter ist keine SortedList .


Nehmen Sie zum Beispiel DirectoryCleaner , ein Objekt, das Ordner im Dateisystem bereinigt. Ist es elegant? Wir sagen jedoch niemals: "Bitten Sie den Ordnerreiniger, D: / Test zu bereinigen" , immer: "D: / Test bereinigen " , damit das Directory mit der Clean natürlicher und genauer aussieht.


Ein lebhafterer Fall ist interessanter: FileSystemWatcher von .NET ist ein Dateisystembeobachter, der Änderungen meldet. Aber warum der ganze Beobachter, wenn die Änderungen selbst berichten können, dass sie passiert sind? Darüber hinaus müssen sie untrennbar mit der Datei oder dem Ordner verbunden sein, sodass sie auch im Directory oder in der File abgelegt werden sollten (mithilfe der Changes Eigenschaft mit der Möglichkeit, file.Changes.OnNext(action) ).


Solche verbalen Namen scheinen das Strategy Entwurfsmuster zu rechtfertigen und weisen an, „die Familie der Algorithmen zu kapseln“ . Wenn wir jedoch anstelle einer „Familie von Algorithmen“ ein authentisches Objekt finden, das in der Geschichte vorhanden ist, werden wir sehen, dass die Strategie nur eine Verallgemeinerung ist.


Um diese und viele andere Fehler zu erklären, wenden wir uns der Philosophie zu.


Existenz geht der Essenz voraus


ItemData , ItemData , AttackOutput , CreationStrategy , StringBuilder , SomethingWrapper , LogBehaviour .


Solche Namen sind durch eines verbunden: Ihr Sein basiert auf Einzelheiten.


Es kommt vor, dass etwas eine Aufgabe schnell stört: etwas fehlt oder ist, aber nichts. Dann denkst du: „Eine Sache, die jetzt X kann, würde mir helfen“ - so wird Existenz gedacht. XImpl X zu "tun", wird XImpl geschrieben - so wird die Entität angezeigt.


Daher ist IIndexedItem oder IItemWithIndex anstelle von IArrayItem häufiger anzutreffen, oder beispielsweise werden in der Reflection-API anstelle der Methode ( Method ) nur Informationen dazu MethodInfo ( MethodInfo ).


Ein wahrerer Weg: Angesichts des Bedürfnisses nach Existenz finden Sie eine Entität, die sie implementiert, und, da dies ihre Natur ist, andere.


Zum Beispiel wollten sie den Wert des Typs string ändern, ohne Zwischeninstanzen zu erstellen - es stellte sich als einfache Lösung in Form von StringBuilder , während es meiner Meinung nach besser geeignet ist - MutableString .


Rufen Sie das Dateisystem auf: DirectoryRenamer zum Umbenennen von Ordnern nicht benötigt, da die Aktion bereits im Code gefunden wurde , sobald Sie dem Vorhandensein des Directory Objekts zustimmen.


Wenn Sie beschreiben möchten, wie eine Sperre vorgenommen wird , müssen Sie nicht klarstellen, dass dies ILockBehaviour oder ILockStrategy , was viel einfacher ist - ILock (mit der Acquire Methode, die IDisposable ) oder ICriticalSection (mit Enter ).


Dies umfasst auch alle Arten von Data , Args , Params , Input , Args , Params (seltener Params ) - Objekte, die völlig verhaltensfrei sind, weil sie als einseitig betrachtet wurden.


Wo die Existenz primär ist, wird der Quotient mit dem Allgemeinen verwechselt und die Namen der Objekte sind verwirrend - Sie müssen jede Zeile durchlesen und herausfinden, wohin der Charakter gegangen ist und warum es nur seine Data .


Bizarre Taxonomie


CalculatorImpl , AbstractHero , ConcreteThing , CharacterBase .


Alle aus den oben beschriebenen Gründen sehen wir manchmal Objekte, für die ein Platz in der Hierarchie genau angegeben ist. Wieder einmal rast die Existenz voran, wieder sehen wir, wie das unmittelbare Bedürfnis hastig in den Code verschüttet wurde, ohne die Konsequenzen zu berücksichtigen.


HumanBase es wirklich eine Person ( Human ) - den Erben der Basisperson ( HumanBase )? Aber wie ist es, wenn Item AbstractItem erbt?


Manchmal wollen sie zeigen, dass es keinen Character , sondern eine Art "rohe" Ähnlichkeit - CharacterRaw .


Impl , Abstract , Custom , Base , Concrete , Internal , Raw - ein Zeichen von Instabilität, Unbestimmtheit der Architektur, die wie die Waffe aus der ersten Szene sicherlich später schießen wird.


Wiederholungen


Bei verschachtelten Typen geschieht dies: RepositoryItem im Repository , WindowState im Window , HeroBuilder in Hero .


Wiederholung schneidet durch die Bedeutung, verschärft Fehler und trägt nur zur Komplexität des Textes bei.


Redundante Teile


Zum Synchronisieren von Threads wird ManualResetEvent häufig mit der folgenden API verwendet:


 public class ManualResetEvent { //   —  `EventWaitHandle`. void Set(); void Reset(); bool WaitOne(); } 

Ich persönlich muss mich jedes Mal daran erinnern, wie sich Set von Reset (unangenehme Grammatik) unterscheidet und was ein „manuell zurückgesetztes Ereignis“ im Zusammenhang mit der Arbeit mit Streams im Allgemeinen ist.


In solchen Fällen ist es einfacher, Metaphern zu verwenden, die weit vom Programmieren entfernt sind (aber dem Alltag nahe kommen):


 public class ThreadGate { void Open(); void Close(); bool WaitForOpen(); } 

Es gibt sicherlich nichts zu verwirren!


Manchmal kommt es zum Lächerlichen: ItemsList Sie an, dass die Elemente nicht nur Elemente sind, sondern unbedingt ItemsList oder ItemsDictionary !


Wenn die ItemsList nicht lustig ist, ist AbstractInterceptorDrivenBeanDefinitionDecorator von Spring in ItemsList . Die Wörter in diesem Namen sind Lumpen, aus denen ein riesiges Monster genäht wird. Obwohl ... Wenn dies ein Monster ist, was ist dann HasThisTypePatternTriedToSneakInSomeGenericOrParameterizedTypePatternMatchingStuffAnywhereVisitor ? Hoffe Vermächtnis .


Neben Klassen- und Schnittstellennamen stößt man häufig auf Redundanz in Variablen oder Feldern.


Ein Feld vom Typ IOrdersRepository heißt beispielsweise _ordersRepository . Aber wie wichtig ist es zu melden, dass Bestellungen vom Repository übermittelt werden? Immerhin viel einfacher - _orders .


Es kommt auch vor, dass sie in LINQ-Abfragen die vollständigen Argumentnamen von Lambda-Ausdrücken schreiben, z. B. Player.Items.Where(item => item.IsWeapon) , obwohl wir dieses Element bereits unter Player.Items . In solchen Fällen verwende ich immer das gleiche Zeichen - x : Player.Items.Where(x => x.IsWeapon) (mit Fortsetzung von y , z wenn dies Funktionen innerhalb von Funktionen sind).


Insgesamt


Ich gebe zu, mit einem solchen Anfang wird es nicht leicht sein, die objektive Wahrheit zu finden. Jemand wird zum Beispiel sagen: Service schreiben oder nicht schreiben ist ein strittiger Punkt, unbedeutend, guter Geschmack, und welchen Unterschied macht es, wenn es funktioniert?


Aber Sie können aus Einwegbechern trinken!


Ich bin überzeugt, dass der Weg zum Inhalt durch die Form führt, und wenn man den Gedanken nicht betrachtet, scheint er weg zu sein. Im Text des Programms funktioniert alles auf die gleiche Weise: Stil, Atmosphäre und Rhythmus tragen dazu bei, sich nicht verwirrt, sondern verständlich und großzügig auszudrücken.


Der Name des Objekts ist nicht nur sein Gesicht, sondern auch sein Selbst. Es bestimmt, ob es ätherisch oder gesättigt, abstrakt oder real, trocken oder animiert sein wird. Der Name ändert sich - der Inhalt ändert sich.


Im nächsten Artikel werden wir über genau diesen Inhalt sprechen und darüber, was passiert.

Source: https://habr.com/ru/post/de447404/


All Articles