Dari seorang penerjemah: Kami menerbitkan
sebuah artikel oleh Camil Lelonek untuk Anda tentang makna keterbacaan kode dan “empati programmer”.
Pernahkah Anda bertanya-tanya siapa yang akan melihat kode Anda? Betapa sulitnya bagi orang lain? Mencoba menentukan keterbacaannya ??
“Orang bodoh mana pun bisa menulis kode yang dimengerti mesin. Tetapi hanya programmer yang baik yang menulis kode yang dimengerti orang juga, ”kata Martin Fowler.
Dari waktu ke waktu, ketika saya melihat beberapa cuplikan kode, saya kehilangan kepercayaan akan keberadaan empati di antara programmer. Anda harus mengerti apa yang saya bicarakan - karena kita masing-masing menemukan kode yang ditulis dengan buruk dan praktis tidak dapat dibaca.
Skillbox merekomendasikan: Kursus praktis dua tahun "Saya seorang Pengembang Web PRO . "
Kami mengingatkan Anda: untuk semua pembaca "Habr" - diskon 10.000 rubel saat mendaftar untuk kursus Skillbox apa pun menggunakan kode promo "Habr".
Baru-baru ini saya melihat sesuatu seperti ini:
defmodule Util.Combinators do def then(a, b) do fn data -> b.(a.(data)) end end def a ~> b, do: a |> then(b) end
Pada prinsipnya, semuanya baik-baik saja di sini: mungkin seseorang hanya memiliki fantasi atau pembuat kode memiliki latar belakang matematika yang kuat. Saya tidak ingin menulis ulang kode ini, tetapi secara tidak sadar saya merasa ada sesuatu yang salah di sini. “Harus ada cara untuk membuatnya lebih baik, untuk memformulasikannya secara berbeda. Saya akan melihat bagaimana semuanya bekerja, "saya pikir begitu. Cukup cepat, saya menemukan ini:
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, sepertinya tidak hanya ~> sedang diimpor, tetapi juga fungsi samb! / 0 dan do_reset! / 1. Ok, mari kita lihat modul Reset:
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
Adapun samb !, Ada beberapa cara untuk membuat situs ini lebih mudah. Namun demikian, berhenti pada titik ini tidak masuk akal. Saya lebih suka fokus pada do_reset! / 1. Fungsi ini mengembalikan fungsi yang mengembalikan argumen dan melakukan reset untuk Initializer; dan namanya sendiri cukup rumit.

Saya memutuskan untuk merekayasa balik kode tersebut. Menurut dokumentasi benchee, before_scenario mengambil input skrip sebagai argumen. Nilai pengembalian menjadi input untuk langkah selanjutnya. Inilah yang dimaksud penulis:
- Menginisialisasi koneksi Postgrex.
- Setel ulang EventStore.
- Menggunakan nilai input sebagai item konfigurasi (berbicara tentang jumlah akun).
- Mempersiapkan data untuk pengujian (mis. Menciptakan pengguna dan memasukkan aplikasi).
- Menggunakan tolok ukur.
Secara umum, semuanya jelas, kode seperti itu mudah ditulis. Saya perhatikan bahwa dalam refactoring saya tidak akan memperlihatkan atau memodifikasi fungsi init, ini tidak terlalu penting di sini.
Langkah pertama adalah menggunakan aliasing secara eksplisit daripada impor implisit. Saya tidak pernah menyukai fitur "ajaib" yang muncul dalam kode saya, meskipun Ecto.Query membuat kueri elegan. Sekarang modul Koneksi kami terlihat seperti ini:
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
Selanjutnya, saya memutuskan untuk menulis "kail", seperti yang disarankan dalam dokumentasi:
before_scenario: fn input -> input endYang masih harus dilakukan adalah menyiapkan data. Hasil akhirnya adalah sebagai berikut:
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)
Apakah kode ini sempurna? Mungkin belum. Tetapi apakah lebih mudah dipahami? Saya harap begitu. Bisakah itu dilakukan segera? Jelas ya.
Apa masalahnya?
Ketika saya mengusulkan solusi kepada penulis, saya mendengar: "Keren." Namun, saya tidak berharap lebih.
Masalahnya adalah kode primer berfungsi. Satu-satunya hal yang membuat saya berpikir tentang perlunya refactoring adalah struktur kode yang terlalu kompleks dan mudah dibaca.
Untuk meyakinkan pengembang lain agar kode mereka dapat dibaca, Anda perlu sesuatu yang meyakinkan. Dan argumen "Saya memutuskan untuk mengulang kode Anda, karena tidak jelas," tidak akan diterima, jawabannya adalah "itu berarti Anda hanya pengembang yang buruk, apa yang bisa saya lakukan ¯ \ _ (ツ) _ / ¯".

Ini adalah masalah (bukan) manajemen
Tidak ada yang terkejut bahwa bisnis mengharapkan hasil dari karyawan. Dan semakin cepat mereka diterima, semakin baik. Manajer biasanya mengevaluasi perangkat lunak dan penulisan dalam hal tenggat waktu, anggaran, kecepatan. Saya tidak mengatakan bahwa ini buruk, saya hanya mencoba menjelaskan mengapa tidak ada kode berkualitas tinggi yang "hanya berfungsi". Faktanya adalah bahwa manajer tidak terlalu tertarik pada kecantikan dan keterbacaan, mereka membutuhkan penjualan, biaya rendah dan hasil yang cepat.
Ketika programmer ditekan, mereka mencari jalan keluar. Paling sering, solusinya adalah membuat "kode kerja" di mana mungkin ada banyak "kruk". Itu dibuat tanpa berpikir bahwa kode perlu dipertahankan di masa depan. Dan kode elegan sangat sulit untuk ditulis dengan cepat. Tidak peduli seberapa berpengalaman programmer itu, ketika pekerjaan dilakukan di bawah tekanan waktu, tidak ada yang berpikir tentang kecantikan.
Situasi ini hanya dapat diselesaikan dengan meyakinkan manajer bahwa kode yang buruk (walaupun berfungsi) mengancam untuk meningkatkan biaya pemeliharaannya dalam waktu dekat. Memperbaiki bug dan menambahkan fitur baru, meninjau, menulis dokumentasi teknis - dalam kasus kode berkualitas rendah, semua ini membutuhkan waktu lebih lama daripada dalam situasi di mana ia elegan dan rasional.

Peran penting empati
Jika Anda mengembangkan perangkat lunak, maka Anda memahami bahwa itu ditujukan untuk orang lain, dan pengembangan dilakukan dalam tim. Dan empati sangat penting di sini.
Kode Anda adalah bentuk komunikasi yang khas. Dalam proses pengembangan arsitektur perangkat lunak masa depan, Anda perlu memikirkan orang-orang yang akan berinteraksi dengan kode Anda.
"Empati programmer" membantu menciptakan kode yang lebih bersih dan lebih rasional, bahkan ketika tenggat waktu habis, dan manajer terus "menghancurkan". Ini membantu untuk memahami bagaimana rasanya mengurai kode orang lain yang tidak dapat dibaca, yang sangat sulit untuk dipahami.

Sebagai kesimpulan
Baru-baru ini saya menulis kode di Elixir:
result = calculate_results() Connection.close(conn) result
Kemudian saya memikirkan metode tap Ruby yang memungkinkan Anda menulis ulang kode ini:
calculate_result().tap do Connection.close(conn) end
IMHO, akan lebih baik untuk melakukan ini tanpa hasil variabel menengah. Saya merenungkan bagaimana ini bisa dilakukan, dan sampai pada kesimpulan berikut:
with result = calculate_results(), Connection.close(conn), do: result
Hasilnya akan sama. Tetapi menggunakan dengan dapat menyebabkan masalah bagi seseorang yang mempelajari kode ini, karena dalam kasus biasa, dengan digunakan secara berbeda.
Karena itu, saya memutuskan untuk meninggalkan semuanya apa adanya, agar tidak berbuat lebih baik untuk diri saya sendiri dengan mengorbankan orang lain. Saya meminta Anda untuk melakukan hal yang sama, tanpa menyulitkan kode yang tak terkira. Sebelum memperkenalkan beberapa fungsi atau variabel yang eksotis, ingatlah bahwa kolega Anda mungkin tidak memahaminya dalam ulasan.
Secara umum, saya sarankan menggunakan prinsip berikut: "Ketika Anda menulis kode ANDA, pikirkan tentang LAIN."