Prinsip Tanggung Jawab Tunggal. Tidak sesederhana kelihatannya

Prinsip Tanggung Jawab Tunggal. Tidak sesederhana kelihatannya Prinsip tanggung jawab tunggal, juga dikenal sebagai prinsip tanggung jawab tunggal,
alias prinsip variabilitas seragam - orang yang sangat licin untuk dipahami dan pertanyaan gugup pada wawancara programmer.

Perkenalan serius pertama saya dengan prinsip ini terjadi pada awal tahun pertama, ketika yang muda dan hijau dibawa ke hutan untuk menjadikan siswa dari larva - siswa sejati.

Di dalam hutan, kami dibagi menjadi beberapa kelompok yang masing-masing terdiri dari 8-9 orang dan mengadakan kompetisi - kelompok mana yang paling cepat meminum sebotol vodka, dengan syarat orang pertama dari kelompok menuangkan vodka ke dalam gelas, yang kedua meminumnya, dan yang ketiga makan camilan. Unit yang telah menyelesaikan operasinya berpindah ke akhir antrian grup.

Kasus dimana ukuran antrian adalah kelipatan tiga merupakan implementasi SRP yang baik.

Definisi 1. Tanggung jawab tunggal.

Definisi resmi Prinsip Tanggung Jawab Tunggal (SRP) menyatakan bahwa setiap entitas memiliki tanggung jawab dan alasan keberadaannya sendiri, dan hanya memiliki satu tanggung jawab.

Perhatikan objek “Peminum” (Peminum).
Untuk menerapkan prinsip SRP, kami akan membagi tanggung jawab menjadi tiga:

  • Satu menuangkan (Operasi Tuang)
  • Satu minuman (Operasi Minum)
  • Seseorang memiliki makanan ringan (Operasi TakeBite)

Masing-masing peserta dalam proses bertanggung jawab atas satu komponen proses, yaitu memiliki satu tanggung jawab atom - untuk minum, menuangkan, atau ngemil.

Lubang minum, pada gilirannya, adalah fasad untuk operasi ini:

сlass Tippler {
    //...
    void Act(){
        _pourOperation.Do() // налить
        _drinkUpOperation.Do() // выпить
        _takeBiteOperation.Do() // закусить
    }
}

Prinsip Tanggung Jawab Tunggal. Tidak sesederhana kelihatannya

Kenapa?

Pemrogram manusia menulis kode untuk manusia kera, dan manusia kera lalai, bodoh, dan selalu terburu-buru. Ia dapat memegang dan memahami sekitar 3 – 7 istilah sekaligus.
Dalam kasus pemabuk, ada tiga istilah berikut. Namun jika kita menulis kodenya dalam satu lembar, maka di dalamnya akan terdapat tangan, kacamata, perkelahian dan perdebatan yang tiada habisnya tentang politik. Dan semua ini akan berada dalam satu metode. Saya yakin Anda pernah melihat kode seperti itu dalam latihan Anda. Bukan ujian jiwa yang paling manusiawi.

Di sisi lain, manusia kera dirancang untuk mensimulasikan objek dunia nyata di kepalanya. Dalam imajinasinya, dia bisa menyatukannya, merakit objek baru darinya, dan membongkarnya dengan cara yang sama. Bayangkan sebuah mobil model lama. Dalam imajinasi Anda, Anda dapat membuka pintu, membuka tutup trim pintu dan melihat mekanisme pengangkatan jendela, di dalamnya akan terdapat roda gigi. Namun Anda tidak dapat melihat semua komponen mesin secara bersamaan, dalam satu "daftar". Setidaknya “manusia monyet” tidak bisa.

Oleh karena itu, pemrogram manusia menguraikan mekanisme yang kompleks menjadi serangkaian elemen yang tidak terlalu rumit dan berfungsi. Namun, hal ini dapat diuraikan dengan cara yang berbeda: di banyak mobil tua, saluran udara masuk ke pintu, dan di mobil modern, kegagalan pada kunci elektronik mencegah mesin untuk hidup, yang dapat menjadi masalah selama perbaikan.

sekarang, SRP adalah prinsip yang menjelaskan BAGAIMANA penguraiannya, yaitu di mana menarik garis pemisahnya.

Ia mengatakan, perlu diurai menurut prinsip pembagian “tanggung jawab”, yaitu menurut tugas-tugas obyek tertentu.

Prinsip Tanggung Jawab Tunggal. Tidak sesederhana kelihatannya

Mari kita kembali ke minuman dan keuntungan yang didapat manusia monyet selama pembusukan:

  • Kode ini menjadi sangat jelas di setiap tingkatan
  • Kode dapat ditulis oleh beberapa programmer sekaligus (masing-masing menulis elemen terpisah)
  • Pengujian otomatis disederhanakan - semakin sederhana elemennya, semakin mudah untuk mengujinya
  • Komposisi kode muncul - Anda dapat menggantinya Operasi Minum ke operasi di mana seorang pemabuk menuangkan cairan ke bawah meja. Atau ganti operasi penuangan dengan operasi di mana Anda mencampur anggur dan air atau vodka dan bir. Tergantung pada kebutuhan bisnis, Anda dapat melakukan semuanya tanpa menyentuh kode metode Tippler.Bertindak.
  • Dari operasi ini Anda dapat melipat pelahap (hanya menggunakan AmbilBitOperasi), Beralkohol (hanya menggunakan Operasi Minum langsung dari botolnya) dan memenuhi banyak persyaratan bisnis lainnya.

(Oh sepertinya ini sudah menjadi prinsip OCP, dan saya melanggar tanggung jawab postingan ini)

Dan, tentu saja, kekurangannya:

  • Kita harus membuat lebih banyak tipe.
  • Seorang pemabuk minum untuk pertama kalinya beberapa jam lebih lambat dari biasanya.

Definisi 2. Variabilitas terpadu.

Izinkan saya, tuan-tuan! Kelas minum juga memiliki satu tanggung jawab - yaitu minum! Dan secara umum, kata “tanggung jawab” adalah konsep yang sangat kabur. Seseorang bertanggung jawab atas nasib umat manusia, dan seseorang bertanggung jawab atas pemeliharaan penguin yang terbalik di tiang.

Mari kita pertimbangkan dua implementasi dari peminum. Yang pertama, disebutkan di atas, berisi tiga kelas - tuang, minum, dan camilan.

Yang kedua ditulis melalui metodologi “Maju dan Hanya Maju” dan berisi semua logika dalam metode tersebut Bertindak:

//Не тратьте время  на изучение этого класса. Лучше съешьте печеньку
сlass BrutTippler {
   //...
   void Act(){
        // наливаем
    if(!_hand.TryDischarge(from:_bottle, to:_glass, size:_glass.Capacity))
        throw new OverdrunkException();

    // выпиваем
    if(!_hand.TryDrink(from: _glass,  size: _glass.Capacity))
        throw new OverdrunkException();

    //Закусываем
    for(int i = 0; i< 3; i++){
        var food = _foodStore.TakeOrDefault();
        if(food==null)
            throw new FoodIsOverException();

        _hand.TryEat(food);
    }
   }
}

Kedua kelas ini, dari sudut pandang pengamat luar, terlihat persis sama dan berbagi tanggung jawab “minum” yang sama.

Kebingungan!

Lalu kita online dan mencari tahu definisi lain dari SRP - Prinsip Perubahan Tunggal.

SCP menyatakan bahwa "Sebuah modul memiliki satu dan hanya satu alasan untuk berubah". Artinya, “Tanggung jawab adalah alasan untuk perubahan.”

(Tampaknya orang-orang yang memberikan definisi aslinya yakin dengan kemampuan telepati manusia kera)

Sekarang semuanya jatuh pada tempatnya. Secara terpisah, kita dapat mengubah tata cara menuang, meminum, dan mengemil, namun pada peminumnya sendiri kita hanya dapat mengubah urutan dan komposisi pengoperasiannya, misalnya dengan memindahkan camilan sebelum diminum atau menambahkan bacaan bersulang.

Dalam pendekatan “Maju dan Hanya Maju”, segala sesuatu yang dapat diubah hanya diubah dalam metodenya Bertindak. Hal ini dapat dibaca dan efektif ketika hanya ada sedikit logika dan jarang berubah, namun seringkali berakhir dengan metode yang buruk, masing-masing 500 baris, dengan pernyataan-jika yang lebih banyak daripada yang dibutuhkan Rusia untuk bergabung dengan NATO.

Definisi 3. Lokalisasi perubahan.

Peminum sering kali tidak mengerti mengapa mereka terbangun di apartemen orang lain, atau di mana ponsel mereka berada. Saatnya menambahkan logging mendetail.

Mari kita mulai login dengan proses penuangan:

class PourOperation: IOperation{
    PourOperation(ILogger log /*....*/){/*...*/}
    //...
    void Do(){
        _log.Log($"Before pour with {_hand} and {_bottle}");
        //Pour business logic ...
        _log.Log($"After pour with {_hand} and {_bottle}");
    }
}

Dengan merangkumnya ke dalam Operasi Tuang, kami bertindak bijak dalam hal tanggung jawab dan enkapsulasi, tetapi sekarang kami bingung dengan prinsip variabilitas. Selain pengoperasiannya sendiri yang bisa berubah, logging itu sendiri juga bisa diubah. Anda harus memisahkan dan membuat logger khusus untuk operasi penuangan:

interface IPourLogger{
    void LogBefore(IHand, IBottle){}
    void LogAfter(IHand, IBottle){}
    void OnError(IHand, IBottle, Exception){}
}

class PourOperation: IOperation{
    PourOperation(IPourLogger log /*....*/){/*...*/}
    //...
    void Do(){
        _log.LogBefore(_hand, _bottle);
        try{
             //... business logic
             _log.LogAfter(_hand, _bottle");
        }
        catch(exception e){
            _log.OnError(_hand, _bottle, e)
        }
    }
}

Pembaca yang cermat akan memperhatikan hal itu LogSetelah, LogSebelumnya и OnError juga dapat diubah secara individual, dan, dengan analogi dengan langkah sebelumnya, akan dibuat tiga kelas: TuangLoggerSebelumnya, TuangLoggerSetelah и TuangErrorLogger.

Dan mengingat ada tiga operasi untuk seorang peminum, kami mendapatkan sembilan kelas logging. Hasilnya, seluruh lingkaran minum terdiri dari 14 kelas (!!!).

Hiperbola? Hampir tidak! Manusia monyet dengan granat dekomposisi akan membagi “penuang” menjadi botol, gelas, operator penuangan, layanan pasokan air, model fisik tumbukan molekul, dan untuk kuartal berikutnya dia akan mencoba melepaskan ketergantungan tanpa variabel global. Dan percayalah, dia tidak akan berhenti.

Di titik inilah banyak yang sampai pada kesimpulan bahwa SRP adalah dongeng dari kerajaan merah muda, dan pergi bermain mie...

... tanpa pernah mengetahui adanya definisi ketiga dari Srp:

“Prinsip Tanggung Jawab Tunggal menyatakan bahwa hal-hal yang mirip dengan perubahan harus disimpan di satu tempat". atau "Perubahan apa yang harus dilakukan bersama-sama harus disimpan di satu tempat"

Artinya, jika kita mengubah logging suatu operasi, maka kita harus mengubahnya di satu tempat.

Ini adalah poin yang sangat penting - karena semua penjelasan SRP di atas mengatakan bahwa jenis-jenis tersebut perlu dihancurkan ketika sedang dihancurkan, yaitu, mereka memberlakukan “batas atas” pada ukuran benda, dan sekarang kita sudah berbicara tentang “batas bawah”. Dengan kata lain, SRP tidak hanya mengharuskan “menghancurkan sambil menghancurkan”, tetapi juga tidak berlebihan – “jangan menghancurkan benda-benda yang saling terkait”. Inilah pertarungan hebat antara pisau cukur Occam dan manusia kera!

Prinsip Tanggung Jawab Tunggal. Tidak sesederhana kelihatannya

Sekarang peminumnya akan merasa lebih baik. Selain tidak perlu membagi logger IPourLogger menjadi tiga kelas, kita juga dapat menggabungkan semua logger menjadi satu jenis:

class OperationLogger{
    public OperationLogger(string operationName){/*..*/}
    public void LogBefore(object[] args){/*...*/}       
    public void LogAfter(object[] args){/*..*/}
    public void LogError(object[] args, exception e){/*..*/}
}

Dan jika kita menambahkan jenis operasi keempat, maka loggingnya sudah siap. Dan kode operasinya sendiri bersih dan bebas dari gangguan infrastruktur.

Hasilnya, kami memiliki 5 kelas untuk memecahkan masalah minum:

  • Operasi penuangan
  • Operasi minum
  • Operasi kemacetan
  • penebang
  • Fasad peminum

Masing-masing dari mereka bertanggung jawab secara ketat atas satu fungsi dan memiliki satu alasan untuk perubahan. Semua aturan serupa dengan perubahan terletak di dekatnya.

Contoh kehidupan nyata

Kami pernah menulis layanan untuk mendaftarkan klien b2b secara otomatis. Dan metode GOD muncul untuk 200 baris konten serupa:

  • Buka 1C dan buat akun
  • Dengan akun ini, buka modul pembayaran dan buat di sana
  • Periksa apakah akun dengan akun tersebut belum dibuat di server utama
  • Buat akun baru
  • Tambahkan hasil registrasi pada modul pembayaran dan nomor 1c pada layanan hasil registrasi
  • Tambahkan informasi akun ke tabel ini
  • Buat nomor titik untuk klien ini di layanan titik. Berikan nomor akun 1c Anda ke layanan ini.

Dan ada sekitar 10 operasi bisnis lainnya dalam daftar ini dengan konektivitas yang buruk. Hampir semua orang membutuhkan objek akun. ID titik dan nama klien diperlukan dalam separuh panggilan.

Setelah satu jam melakukan refactoring, kami dapat memisahkan kode infrastruktur dan beberapa nuansa bekerja dengan akun ke dalam metode/kelas terpisah. Metode God membuatnya lebih mudah, namun masih ada 100 baris kode tersisa yang tidak ingin diuraikan.

Hanya setelah beberapa hari menjadi jelas bahwa inti dari metode “ringan” ini adalah algoritma bisnis. Dan deskripsi asli spesifikasi teknisnya cukup rumit. Dan upaya untuk memecah metode ini menjadi beberapa bagianlah yang akan melanggar SRP, dan bukan sebaliknya.

Formalisme.

Sudah waktunya untuk meninggalkan mabuk kita sendirian. Keringkan air mata Anda - kami pasti akan kembali lagi suatu hari nanti. Sekarang mari kita formalkan pengetahuan dari artikel ini.

Formalisme 1. Pengertian SRP

  1. Pisahkan unsur-unsurnya sehingga masing-masing bertanggung jawab atas satu hal.
  2. Tanggung jawab berarti “alasan untuk berubah.” Artinya, setiap elemen hanya mempunyai satu alasan untuk berubah, dalam hal logika bisnis.
  3. Potensi perubahan pada logika bisnis. harus dilokalisasi. Elemen yang berubah secara serempak harus berada di dekatnya.

Formalisme 2. Kriteria uji mandiri yang diperlukan.

Saya belum melihat kriteria yang memadai untuk memenuhi SRP. Tetapi ada syarat-syarat yang diperlukan:

1) Tanyakan pada diri Anda apa yang dilakukan kelas/metode/modul/layanan ini. Anda harus menjawabnya dengan definisi sederhana. ( Terima kasih Brightori )

penjelasan

Namun, terkadang sangat sulit menemukan definisi sederhana

2) Memperbaiki bug atau menambahkan fitur baru mempengaruhi jumlah minimum file/kelas. Idealnya - satu.

penjelasan

Karena tanggung jawab (untuk suatu fitur atau bug) dirangkum dalam satu file/kelas, Anda tahu persis di mana mencarinya dan apa yang harus diedit. Misalnya: fitur mengubah keluaran operasi logging hanya memerlukan perubahan logger. Tidak perlu menelusuri sisa kode.

Contoh lainnya adalah menambahkan kontrol UI baru, mirip dengan yang sebelumnya. Jika hal ini memaksa Anda untuk menambahkan 10 entitas berbeda dan 15 konverter berbeda, sepertinya Anda berlebihan.

3) Jika beberapa pengembang sedang mengerjakan fitur berbeda dari proyek Anda, maka kemungkinan konflik penggabungan, yaitu kemungkinan file/kelas yang sama akan diubah oleh beberapa pengembang pada saat yang sama, adalah kecil.

penjelasan

Jika, ketika menambahkan operasi baru "Tuangkan vodka di bawah meja", Anda perlu memengaruhi logger, operasi minum dan menuangkan, maka sepertinya tanggung jawab dibagi secara tidak benar. Tentu saja, hal ini tidak selalu memungkinkan, namun kita harus berusaha mengurangi angka ini.

4) Ketika ditanya pertanyaan klarifikasi tentang logika bisnis (dari pengembang atau manajer), Anda hanya masuk ke satu kelas/file dan menerima informasi hanya dari sana.

penjelasan

Fitur, aturan, atau algoritme ditulis secara kompak, masing-masing di satu tempat, dan tidak tersebar dengan tanda di seluruh ruang kode.

5) Penamaannya jelas.

penjelasan

Kelas atau metode kita bertanggung jawab atas satu hal, dan tanggung jawab tersebut tercermin dalam namanya

AllManagersManagerService - kemungkinan besar kelas Dewa
Pembayaran Lokal - mungkin tidak

Formalisme 3. Metodologi pengembangan Occam-first.

Pada awal desain, manusia kera tidak mengetahui dan tidak merasakan seluruh seluk-beluk masalah yang sedang dipecahkan dan dapat melakukan kesalahan. Anda dapat membuat kesalahan dengan berbagai cara:

  • Buat objek terlalu besar dengan menggabungkan tanggung jawab yang berbeda
  • Membingkai ulang dengan membagi satu tanggung jawab menjadi beberapa jenis yang berbeda
  • Salah mendefinisikan batas tanggung jawab

Penting untuk mengingat aturan: “lebih baik membuat kesalahan besar,” atau “jika Anda tidak yakin, jangan berpisah.” Jika, misalnya, kelas Anda berisi dua tanggung jawab, maka hal tersebut masih dapat dimengerti dan dapat dibagi menjadi dua dengan sedikit perubahan pada kode klien. Merakit kaca dari pecahan kaca biasanya lebih sulit karena konteksnya tersebar di beberapa file dan kurangnya ketergantungan yang diperlukan dalam kode klien.

Sudah waktunya untuk mengakhiri hari ini

Ruang lingkup SRP tidak terbatas pada OOP dan SOLID. Ini berlaku untuk metode, fungsi, kelas, modul, layanan mikro, dan layanan. Hal ini berlaku untuk pengembangan “figax-figax-and-prod” dan “ilmu roket”, sehingga membuat dunia menjadi sedikit lebih baik di mana pun. Jika Anda memikirkannya, ini hampir merupakan prinsip dasar dari semua teknik. Teknik mesin, sistem kendali, dan tentu saja semua sistem kompleks dibangun dari komponen-komponen, dan “underfragmentasi” menghilangkan fleksibilitas para perancang, “fragmentasi berlebihan” menghilangkan efisiensi para perancang, dan batasan yang salah membuat mereka kehilangan akal dan ketenangan pikiran.

Prinsip Tanggung Jawab Tunggal. Tidak sesederhana kelihatannya

SRP tidak ditemukan secara alami dan bukan bagian dari ilmu pasti. Ini mendobrak keterbatasan biologis dan psikologis kita. Ini hanyalah sebuah cara untuk mengendalikan dan mengembangkan sistem yang kompleks dengan menggunakan otak manusia kera. Dia memberi tahu kita cara menguraikan suatu sistem. Formulasi aslinya memerlukan telepati yang cukup banyak, tetapi saya harap artikel ini dapat menjelaskan sebagian dari tabir asap tersebut.

Sumber: www.habr.com

Tambah komentar