Anda harus membaca kode untuk menemani program, dan semakin mudah untuk melakukannya, semakin terlihat seperti bahasa alami - maka Anda akan masuk lebih cepat dan fokus pada hal utama.
Dalam dua artikel terakhir, saya menunjukkan bahwa kata-kata yang dipilih dengan cermat membantu untuk lebih memahami esensi dari apa yang ditulis, tetapi hanya memikirkannya saja tidak cukup, karena setiap kata ada dalam dua bentuk: seperti dalam dirinya sendiri dan sebagai bagian dari kalimat. Mengulang CurrentThread
belum berulang sampai kita membacanya dalam konteks Thread.CurrentThread
.
Dengan demikian, dipandu oleh not dan melodi sederhana, kita sekarang akan melihat apa itu musik.
Siklus daftar isi
- Benda-benda
- Tindakan dan properti
- Kode sebagai teks
Kode sebagai teks
Kebanyakan antarmuka yang lancar dirancang dengan penekanan pada eksternal daripada internal, sehingga sangat mudah dibaca. Tentu saja, tidak gratis: isinya melemah. Jadi, katakanlah, dalam paket FluentAssertions
dapat menulis: (2 + 2).Should().Be(4, because: "2 + 2 is 4!")
, Dan, relatif terhadap membaca, because
terlihat elegan, tetapi di dalam Be()
lebih tepatnya, parameter error
atau errorMessage
.
Menurut pendapat saya, pengecualian seperti itu tidak signifikan. Ketika kami sepakat bahwa kode tersebut adalah teks, komponen-komponennya tidak lagi menjadi milik mereka: mereka sekarang menjadi bagian dari semacam "Eter" universal.
Saya akan menunjukkan dengan contoh bagaimana pertimbangan seperti itu menjadi pengalaman.
Interlocked
Biarkan saya mengingatkan Anda tentang Interlocked.CompareExchange(ref x, newX, oldX)
Interlocked
, yang kami beralih dari Interlocked.CompareExchange(ref x, newX, oldX)
ke Atomically.Change(ref x, from: oldX, to: newX)
, menggunakan nama metode dan parameter yang jelas.
ExceptWith
Tipe ISet<>
memiliki metode yang disebut ExceptWith
. Jika Anda melihat panggilan seperti items.ExceptWith(other)
, Anda tidak akan segera menyadari apa yang terjadi. Tapi Anda hanya perlu menulis: items.Exclude(other)
, karena semuanya jatuh pada tempatnya.
GetValueOrDefault
Saat bekerja dengan Nullable<T>
memanggil x.Value
akan mengeluarkan pengecualian jika x
adalah null
. Jika Anda masih perlu mendapatkan Value
, gunakan x.GetValueOrDefault
: itu adalah Value
atau nilai default. Tebal.
Ekspresi "atau x, atau nilai default" cocok dengan x.OrDefault
pendek dan elegan.
int? x = null; var a = x.GetValueOrDefault();
Dengan OrDefault
dan Or
ada satu hal yang patut diingat: ketika bekerja dengan operator .?
Anda tidak dapat menulis sesuatu seperti x?.IsEnabled.Or(false)
, hanya (x?.IsEnabled).Or(false)
(dengan kata lain, operator .?
Membatalkan seluruh sisi kanan jika null
di sebelah kiri).
Templat dapat diterapkan saat bekerja dengan IEnumerable<T>
:
IEnumerable<int> numbers = null;
Math.Min
dan Math.Max
Sebuah ide dengan Or
dapat dikembangkan menjadi tipe numerik. Misalkan Anda ingin mengambil angka maksimum dari a
dan b
. Kemudian kita menulis: Math.Max(a, b)
atau a > b ? a : b
a > b ? a : b
. Kedua opsi terlihat cukup akrab, tetapi, bagaimanapun, tidak terlihat seperti bahasa alami.
Anda dapat menggantinya dengan: a.Or(b).IfLess()
- ambil a
atau b
jika a
kurang . Cocok untuk situasi seperti itu:
Creature creature = ...; int damage = ...;
string.Join
Terkadang Anda perlu merakit urutan menjadi string, memisahkan elemen dengan spasi atau koma. Untuk melakukan ini, gunakan string.Join
, misalnya, seperti ini: string.Join(", ", new [] { 1, 2, 3 }); // "1, 2, 3".
string.Join(", ", new [] { 1, 2, 3 }); // "1, 2, 3".
.
Sederhana "Bagi nomor koma" tiba-tiba menjadi "Lampirkan koma ke setiap nomor dari daftar" - ini tentu bukan kode sebagai teks.
var numbers = new [] { 1, 2, 3 };
Regex
Namun, string.Join
sangat tidak berbahaya dibandingkan dengan bagaimana Regex
kadang Regex
kadang digunakan secara tidak benar dan untuk tujuan lain. Di mana Anda dapat bertahan dengan teks sederhana dan mudah dibaca, karena alasan tertentu entri yang terlalu rumit lebih disukai.
Mari kita mulai dengan yang sederhana - menentukan bahwa string mewakili sekumpulan angka:
string id = ...;
Kasus lain adalah untuk mengetahui apakah ada setidaknya satu karakter dalam string dari urutan:
string text = ...;
Semakin rumit tugasnya, semakin sulit solusi "pola": untuk membagi catatan jenis "HelloWorld"
menjadi beberapa kata "Hello World"
, seseorang bukannya algoritma sederhana menginginkan monster:
string text = ...;
Tidak diragukan lagi, ekspresi reguler efektif dan universal, tetapi saya ingin memahami apa yang terjadi pada pandangan pertama.
Substring
dan Remove
Itu terjadi bahwa Anda perlu menghapus beberapa bagian dari baris dari awal atau akhir, misalnya, dari path
- .txt
ekstensi, jika ada.
string path = ...;
Sekali lagi, tindakan dan algoritme hilang, dan garis sederhana dibiarkan tanpa ekstensi .exe di akhir .
Karena metode Without
harus mengembalikan WithoutExpression
tertentu, mereka memohon untuk yang lain: path.Without("_").AtStart
dan path.Without("_").AtStart
path.Without("Something").Anywhere
. Menarik juga bahwa ungkapan lain dapat dikonstruksikan dengan kata yang sama: name.Without(charAt: 1)
- menghapus karakter pada indeks 1 dan mengembalikan baris baru (berguna dalam menghitung permutasi). Dan juga bisa dibaca!
Type.GetMethods
Untuk mendapatkan metode jenis tertentu menggunakan refleksi, gunakan:
Type type = ...;
(Hal yang sama berlaku untuk GetFields
dan GetProperties
.)
Directory.Copy
Semua operasi dengan folder dan file sering digeneralisasi ke DirectoryUtils
, FileSystemHelper
. Mereka menerapkan bypass sistem file, pembersihan, penyalinan, dll. Tetapi di sini Anda dapat menemukan sesuatu yang lebih baik!
Kami menampilkan teks "salin semua file dari 'D: \ Source' ke 'D: \ Target'" ke kode "D:\\Source".AsDirectory().Copy().Files.To("D:\\Target")
AsDirectory()
- mengembalikan DirectoryInfo
dari string
, dan Copy()
- membuat instance CopyExpression
yang menjelaskan API unik untuk membangun ekspresi (Anda tidak dapat memanggil Copy().Files.Files
, misalnya). Maka kesempatan terbuka untuk menyalin tidak semua file, tetapi beberapa: Copy().Files.Where(x => x.IsNotEmpty)
.
GetOrderById
Pada artikel kedua, saya menulis bahwa IUsersRepository.GetUser(int id)
berlebihan, dan lebih baik, IUsersRepository.User(int id)
. Dengan demikian, dalam IOrdersRepository
serupa IOrdersRepository
kami belum GetOrderById(int id)
, tetapi Order(int id)
. Namun, dalam contoh lain, disarankan agar variabel repositori seperti itu disebut bukan _ordersRepository
, tetapi hanya _orders
.
Kedua perubahan itu baik pada mereka sendiri, tetapi mereka tidak dijumlahkan bersama dalam konteks membaca: menelepon _orders.Order(id)
terlihat bertele-tele. Mungkin untuk _orders.Get(id)
, tetapi pesanan gagal, kami hanya ingin menentukan yang memiliki pengidentifikasi seperti itu . Satu itu adalah One
, oleh karena itu:
IOrdersRepository orders = ...; int id = ...;
GetOrders
Dalam objek seperti IOrdersRepository
, metode lain sering ditemukan: AddOrder
, RemoveOrder
, GetOrders
. Dua pengulangan pertama hilang, dan Add
dan Remove
diperoleh (dengan entri _orders.Add(order)
dan _orders.Remove(order)
) yang sesuai. Dengan GetOrders
agak sulit untuk mengubah nama Orders
sedikit. Mari kita lihat:
IOrdersRepository orders = ...;
Perlu dicatat bahwa dengan _ordersRepository
lama _ordersRepository
pengulangan di GetOrders
atau GetOrderById
tidak begitu terlihat, karena kami bekerja dengan repositori!
Nama seperti One
, All
cocok untuk banyak antarmuka yang mewakili banyak. Katakanlah, dalam implementasi API GitHub yang terkenal - octokit
- mendapatkan semua repositori pengguna terlihat seperti gitHub.Repository.GetAllForUser("John")
, meskipun lebih logis - gitHub.Users.One("John").Repositories.All
. Dalam hal ini, mendapatkan satu repositori akan menjadi, masing-masing, gitHub.Repository.Get("John", "Repo")
alih-alih gitHub.Users.One("John").Repositories.One("Repo")
jelas. gitHub.Users.One("John").Repositories.One("Repo")
. Kasus kedua terlihat lebih panjang, tetapi konsisten secara internal dan mencerminkan platform. Selain itu, dengan menggunakan metode ekstensi, dapat disingkat menjadi gitHub.User("John").Repository("Repo")
.
Dictionary.TryGetValue
Memperoleh nilai dari kamus dibagi menjadi beberapa skenario yang hanya berbeda dalam apa yang perlu dilakukan jika kunci tidak ditemukan:
- melempar kesalahan (
dictionary[key]
); - mengembalikan nilai default (tidak diterapkan, tetapi sering menulis
GetValueOrDefault
atau TryGetValue
); - kembalikan sesuatu yang lain (tidak diterapkan, tapi saya harapkan
GetValueOrOther
); - tulis nilai yang ditentukan ke kamus dan kembalikan (tidak diterapkan, tetapi
GetOrAdd
).
Ekspresi bertemu pada titik " ambil X, atau Y jika X tidak ." Selain itu, seperti dalam kasus _ordersRepository
, kita akan memanggil variabel kamus bukan itemsDictionary
, tetapi items
.
Kemudian untuk bagian "ambil beberapa X" , panggilan items.One(withKey: X)
formulir. items.One(withKey: X)
ideal, mengembalikan struktur dengan empat ujung :
Dictionary<int, Item> items = ...; int id = ...;
Assembly.GetTypes
Mari kita lihat cara membuat semua contoh tipe T
dalam perakitan:
Jadi, kadang-kadang, nama kelas statis adalah awal dari sebuah ekspresi.
Sesuatu yang serupa dapat ditemukan di NUnit: Assert.That(2 + 2, Is.EqualTo(4))
- Is
dan tidak dipahami sebagai tipe mandiri.
Argument.ThrowIfNull
Sekarang lihat pada pemeriksaan prasyarat:
Ensure.NotNull(argument)
- bagus, tetapi tidak cukup bahasa Inggris. Hal lain adalah Ensure(that: x).NotNull()
ditulis di atas. Kalau saja ada ...
Ngomong-ngomong, kamu bisa! Kami menulis Contract.Ensure(that: argument).IsNotNull()
dan impor jenis Contract
using static
. Jadi segala macam Ensure(that: type).Implements<T>()
, Ensure(that: number).InRange(from: 5, to: 10)
, dll Ensure(that: number).InRange(from: 5, to: 10)
.
Gagasan impor statis membuka banyak pintu. Contoh yang bagus untuk kepentingan: alih-alih Remove(x, from: items)
. Remove(x, from: items)
items.Remove(x)
tulis Remove(x, from: items)
. Namun rasa ingin tahu adalah pengurangan enum
dan properti yang mengembalikan fungsi.
IItems items = ...;
Find
Eksotis
Dalam C # 7.1 dan lebih tinggi, Anda dapat menulis bukan Find(1, @in: items)
, tetapi Find(1, in items)
, di mana Find
didefinisikan sebagai Find<T>(T item, in IEnumerable<T> items)
. Contoh ini tidak praktis, tetapi menunjukkan bahwa semua sarana bagus dalam perjuangan untuk keterbacaan.
Total
Pada bagian ini, saya melihat beberapa cara untuk bekerja dengan keterbacaan kode. Semuanya dapat digeneralisasi ke:
- Parameter bernama sebagai bagian dari ekspresi adalah
Should().Be(4, because: "")
, Atomically.Change(ref x, from: oldX, to: newX)
. Atomically.Change(ref x, from: oldX, to: newX)
. - Nama sederhana alih-alih detail teknis
Separated(with: ", ")
, Exclude
. - Metode sebagai bagian dari variabel adalah
x.OrDefault()
, x.Or(b).IfLess()
, orders.One(with: id)
, orders.All
. - Metode sebagai bagian dari ekspresi adalah
path.Without(".exe").AtEnd
. - Jenis sebagai bagian dari ekspresi adalah
Instances.Of
, Is.EqualTo
. - Metode sebagai bagian dari ekspresi (
using static
) adalah Ensure(that: x)
, items.All(Weapons)
.
Dengan demikian, yang eksternal dan yang direnungkan dibawa ke permukaan. Mula-mula ia dianggap, dan kemudian inkarnasi spesifiknya dipikirkan, tidak begitu penting, selama kode itu dibaca sebagai teks. Oleh karena itu, juri tidak begitu menyukai bahasa - ia menentukan perbedaan antara item.GetValueOrDefault
dan item.OrDefault
.
Epilog
Mana yang lebih baik, jelas, tetapi bukan metode kerja, atau bekerja, tetapi tidak bisa dipahami? Kastil berwarna putih salju tanpa perabotan dan kamar atau gudang dengan gaya Louis XIV? Kapal pesiar mewah tanpa mesin atau tongkang mengerang dengan komputer kuantum yang tidak dapat digunakan siapa pun?
Jawaban kutub tidak cocok, tetapi "di suatu tempat di tengah" juga.
Menurut pendapat saya, kedua konsep itu tidak dapat dipisahkan: dengan hati-hati memilih sampul buku, kami ragu-ragu melihat kesalahan dalam teks, dan sebaliknya. Saya tidak ingin The Beatles memainkan musik berkualitas rendah, tetapi mereka juga harus disebut MusicHelper .
Hal lain adalah bahwa mengerjakan kata sebagai bagian dari proses pembangunan adalah hal yang diremehkan, hal yang tidak biasa, dan oleh karena itu semacam penilaian ekstrem masih diperlukan. Siklus ini adalah bentuk dan gambar yang ekstrem.
Terima kasih atas perhatian Anda!
Referensi
Siapa pun yang tertarik melihat lebih banyak contoh dapat ditemukan di GitHub saya, misalnya, di perpustakaan Pocket.Common
. (tidak untuk penggunaan di seluruh dunia dan universal)