Von einem Übersetzer: Wir haben für Sie
einen Artikel von Camil Lelonek über die Bedeutung von Lesbarkeit von Code und „Empathie von Programmierern“ veröffentlicht.
Haben Sie sich jemals gefragt, wer Ihren Code anzeigen wird? Wie schwierig kann es für andere sein? Versucht, seine Lesbarkeit zu bestimmen?
„Jeder Dummkopf kann Code schreiben, den die Maschine versteht. Aber nur gute Programmierer schreiben Code, den die Leute auch verstehen “, sagt Martin Fowler.
Von Zeit zu Zeit verliere ich das Vertrauen in die Existenz von Empathie unter Programmierern, wenn ich einige Codefragmente sehe. Sie müssen verstehen, wovon ich spreche - denn jeder von uns ist auf Code gestoßen, der nur schrecklich geschrieben und praktisch unlesbar war.
Skillbox empfiehlt: Zweijähriger Praktikumskurs "Ich bin ein PRO-Webentwickler . "
Wir erinnern Sie daran: Für alle Leser von „Habr“ - ein Rabatt von 10.000 Rubel bei der Anmeldung für einen Skillbox-Kurs mit dem Promo-Code „Habr“.
Ich habe kürzlich so etwas gesehen:
defmodule Util.Combinators do def then(a, b) do fn data -> b.(a.(data)) end end def a ~> b, do: a |> then(b) end
Im Prinzip ist hier alles in Ordnung: Vielleicht hat jemand nur eine Fantasie oder der Autor des Codes hat einen soliden mathematischen Hintergrund. Ich wollte diesen Code nicht umschreiben, aber unbewusst schien es mir, dass hier etwas nicht stimmte. „Es muss einen Weg geben, es besser zu machen, es anders zu formulieren. Ich werde sehen, wie alles funktioniert “, dachte ich mir. Ziemlich schnell fand ich Folgendes:
import Util.{Reset, Combinators} # ... conn = conn!() Benchee.run( # ... time: 40, warmup: 10, inputs: inputs, before_scenario: do_reset!(conn) ~> init, formatter_options: %{console: %{extended_statistics: true}} )
Hmmm, es sieht so aus, als würde nicht nur ~> importiert, sondern auch die Funktionen conn! / 0 und do_reset! / 1. Ok, schauen wir uns das Reset-Modul an:
defmodule Util.Reset do alias EventStore.{Config, Storage.Initializer} def conn! do {:ok, conn} = Config.parsed() |> Config.default_postgrex_opts() |> Postgrex.start_link() conn end def do_reset!(conn) do fn data -> Initializer.reset!(conn) data end end end
Für conn! Gibt es verschiedene Möglichkeiten, diese Website zu vereinfachen. Dennoch macht es keinen Sinn, an dieser Stelle anzuhalten. Ich möchte mich lieber auf do_reset! / 1 konzentrieren. Diese Funktion gibt eine Funktion zurück, die ein Argument zurückgibt und einen Reset für den Initializer durchführt. und der Name selbst ist ziemlich kompliziert.

Ich habe mich entschlossen, den Code zurückzuentwickeln. Laut Benchee-Dokumentation verwendet before_scenario die Skripteingabe als Argument. Der Rückgabewert wird zur Eingabe für die nächsten Schritte. Folgendes hat der Autor wahrscheinlich gemeint:
- Initialisieren einer Postgrex-Verbindung.
- EventStore zurücksetzen.
- Verwenden von Eingabewerten als Konfigurationselemente (über die Anzahl der Konten).
- Vorbereiten von Daten für Tests (d. H. Erstellen von Benutzern und Eingeben der Anwendung).
- Benchmarks verwenden.
Im Allgemeinen ist alles klar, ein solcher Code ist einfach zu schreiben. Ich stelle fest, dass ich beim Refactoring die Init-Funktion nicht anzeigen oder ändern werde, dies ist hier nicht sehr wichtig.
Der erste Schritt besteht darin, Aliasing anstelle impliziter Importe explizit zu verwenden. Ich mochte die "magischen" Funktionen, die in meinem Code erscheinen, nie, obwohl Ecto.Query die Abfragen elegant macht. Jetzt sieht unser Verbindungsmodul folgendermaßen aus:
defmodule Benchmarks.Util.Connection do alias EventStore.{Config, Storage.Initializer} def init! do with {:ok, conn} = Config.parsed() |> Config.default_postgrex_opts() |> Postgrex.start_link() do conn end end def reset!(conn), do: Initializer.reset!(conn) end
Als nächstes habe ich beschlossen, einen „Haken“ zu schreiben, wie in der Dokumentation vorgeschlagen:
before_scenario: fn Eingänge -> Eingänge endenEs bleibt nur noch die Vorbereitung der Daten. Das Endergebnis ist wie folgt:
alias Benchmarks.Util.Connection conn = Connection.init!() # ... Benchee.run( inputs: inputs, before_scenario: fn inputs -> Connection.reset!(conn) init.(inputs) end, formatter_options: %{console: %{extended_statistics: true}} ) Connection.reset!(conn)
Ist dieser Code perfekt? Wahrscheinlich noch nicht. Aber ist es leichter zu verstehen? Ich hoffe darauf. Könnte es sofort gemacht werden? Auf jeden Fall ja.
Was ist das Problem?
Als ich dem Autor eine Lösung vorschlug, hörte ich: „Cool.“ Mehr habe ich jedoch nicht erwartet.
Das Problem ist, dass der primäre Code funktioniert hat. Das einzige, was mich dazu brachte, über die Notwendigkeit eines Refactorings nachzudenken, war die zu komplexe Struktur des Codes und seine geringe Lesbarkeit.
Um andere Entwickler davon zu überzeugen, ihren Code lesbar zu machen, benötigen Sie etwas Überzeugendes. Und das Argument „Ich habe beschlossen, Ihren Code zu wiederholen, weil er dunkel ist“ wird nicht akzeptiert. Die Antwort lautet: „Es bedeutet, dass Sie nur ein schlechter Entwickler sind. Was kann ich tun? ¯ \ _ (ツ) _ / ¯“.

Dies ist ein (Nicht-) Managementproblem
Niemand ist überrascht, dass das Unternehmen Ergebnisse von Mitarbeitern erwartet. Und je früher sie empfangen werden, desto besser. Manager bewerten Software und ihr Schreiben in der Regel in Bezug auf Fristen, Budget und Geschwindigkeit. Ich sage nicht, dass dies schlecht ist, ich versuche nur zu erklären, warum es keinen zu hochwertigen Code gibt, der "einfach funktioniert". Tatsache ist, dass Manager nicht sehr an Schönheit und Lesbarkeit interessiert sind, sondern Verkäufe, niedrige Kosten und schnelle Ergebnisse benötigen.
Wenn Programmierer gedrückt werden, suchen sie einen Ausweg. Meistens besteht die Lösung darin, einen „Arbeitscode“ zu erstellen, in dem sich möglicherweise eine Reihe von „Krücken“ befinden. Es wird ohne den Gedanken erstellt, dass der Code in Zukunft beibehalten werden muss. Und eleganter Code ist sehr schwer schnell zu schreiben. Es spielt keine Rolle, wie erfahren der Programmierer ist, wenn die Arbeit unter Zeitdruck erledigt wird, denkt niemand an Schönheit.
Diese Situation kann nur gelöst werden, indem Manager davon überzeugt werden, dass ein schlechter (wenn auch funktionierender) Code in naher Zukunft die Kosten für seine Wartung zu erhöhen droht. Beheben von Fehlern und Hinzufügen neuer Funktionen, Überprüfen, Schreiben technischer Dokumentation - bei Code mit schlechter Qualität dauert dies viel länger als in einer Situation, in der es elegant und rational ist.

Die wichtige Rolle von Empathie
Wenn Sie Software entwickeln, verstehen Sie, dass diese für andere Personen bestimmt ist und die Entwicklung in einem Team durchgeführt wird. Und Empathie ist hier sehr wichtig.
Ihr Code ist eine besondere Form der Kommunikation. Bei der Entwicklung der Architektur zukünftiger Software müssen Sie an diejenigen denken, die mit Ihrem Code interagieren.
"Empathie der Programmierer" hilft dabei, einen saubereren und rationaleren Code zu erstellen, selbst wenn die Fristen abgelaufen sind und der Manager ständig "zerquetscht". Es hilft zu verstehen, wie es ist, den unlesbaren Code eines anderen zu analysieren, was äußerst schwer zu verstehen ist.

Als Fazit
Ich habe kürzlich Code auf Elixir geschrieben:
result = calculate_results() Connection.close(conn) result
Dann dachte ich an eine Ruby-Tap-Methode, mit der Sie diesen Code neu schreiben können:
calculate_result().tap do Connection.close(conn) end
IMHO wäre es besser, dies ohne das Ergebnis der Zwischenvariablen zu tun. Ich überlegte, wie dies getan werden könnte, und kam zu folgendem Schluss:
with result = calculate_results(), Connection.close(conn), do: result
Das Ergebnis wird das gleiche sein. Die Verwendung von with kann jedoch zu Problemen für jemanden führen, der diesen Code studiert, da with im Normalfall anders verwendet wird.
Deshalb habe ich beschlossen, alles so zu lassen, wie es war, um es nicht auf Kosten anderer besser zu machen. Ich bitte Sie, dasselbe zu tun, ohne den Code unermesslich zu komplizieren. Denken Sie vor der Einführung einer exotischen Funktion oder Variablen daran, dass Ihre Kollegen sie in einer Überprüfung möglicherweise einfach nicht verstehen.
Im Allgemeinen empfehle ich das folgende Prinzip: "Wenn Sie IHREN Code schreiben, denken Sie an ANDERE."