Prinsip Tanggungjawab Tunggal. Tidak semudah yang disangka

Prinsip Tanggungjawab Tunggal. Tidak semudah yang disangka Prinsip tanggungjawab tunggal, juga dikenali sebagai prinsip tanggungjawab tunggal,
aka prinsip kebolehubahan seragam - seorang lelaki yang sangat licin untuk difahami dan soalan yang gugup pada temu bual pengaturcara.

Perkenalan serius pertama saya dengan prinsip ini berlaku pada awal tahun pertama, apabila yang muda dan hijau dibawa ke hutan untuk menjadikan pelajar daripada larva - pelajar sebenar.

Di dalam hutan, kami dibahagikan kepada kumpulan 8-9 orang setiap satu dan mengadakan pertandingan - kumpulan manakah yang paling cepat meminum sebotol vodka, dengan syarat orang pertama dari kumpulan itu menuangkan vodka ke dalam gelas, yang kedua meminumnya, dan yang ketiga mempunyai makanan ringan. Unit yang telah menyelesaikan operasinya bergerak ke penghujung baris gilir kumpulan.

Kes di mana saiz baris gilir adalah gandaan tiga adalah pelaksanaan SRP yang baik.

Definisi 1. Tanggungjawab tunggal.

Takrifan rasmi Prinsip Tanggungjawab Tunggal (SRP) menyatakan bahawa setiap entiti mempunyai tanggungjawab dan sebab kewujudannya sendiri, dan ia hanya mempunyai satu tanggungjawab.

Pertimbangkan objek "Peminum" (Petua Petua).
Untuk melaksanakan prinsip SRP, kami akan membahagikan tanggungjawab kepada tiga:

  • Satu tuang (PourOperation)
  • Satu minuman (DrinkUpOperation)
  • Seseorang mempunyai makanan ringan (TakeBiteOperation)

Setiap peserta dalam proses bertanggungjawab untuk satu komponen proses, iaitu, mempunyai satu tanggungjawab atom - untuk minum, tuang atau snek.

Lubang minum pula adalah fasad untuk operasi ini:

сlass Tippler {
    //...
    void Act(){
        _pourOperation.Do() // Π½Π°Π»ΠΈΡ‚ΡŒ
        _drinkUpOperation.Do() // Π²Ρ‹ΠΏΠΈΡ‚ΡŒ
        _takeBiteOperation.Do() // Π·Π°ΠΊΡƒΡΠΈΡ‚ΡŒ
    }
}

Prinsip Tanggungjawab Tunggal. Tidak semudah yang disangka

Mengapa?

Pengaturcara manusia menulis kod untuk lelaki beruk, dan lelaki beruk itu lalai, bodoh dan sentiasa tergesa-gesa. Dia boleh memegang dan memahami lebih kurang 3 - 7 istilah dalam satu masa.
Dalam kes pemabuk, terdapat tiga istilah ini. Walau bagaimanapun, jika kita menulis kod dengan satu helaian, maka ia akan mengandungi tangan, cermin mata, pergaduhan dan pertengkaran yang tidak berkesudahan tentang politik. Dan semua ini akan berada dalam badan satu kaedah. Saya pasti anda pernah melihat kod sedemikian dalam amalan anda. Bukan ujian yang paling berperikemanusiaan untuk jiwa.

Sebaliknya, lelaki beruk itu direka untuk mensimulasikan objek dunia sebenar di kepalanya. Dalam imaginasinya, dia boleh menolaknya bersama-sama, memasang objek baharu daripadanya, dan membukanya dengan cara yang sama. Bayangkan kereta model lama. Dalam imaginasi anda, anda boleh membuka pintu, membuka trim pintu dan melihat di sana mekanisme angkat tingkap, di dalamnya akan ada gear. Tetapi anda tidak dapat melihat semua komponen mesin pada masa yang sama, dalam satu "penyenaraian". Sekurang-kurangnya "lelaki monyet" tidak boleh.

Oleh itu, pengaturcara manusia menguraikan mekanisme kompleks menjadi satu set elemen yang kurang kompleks dan berfungsi. Walau bagaimanapun, ia boleh diuraikan dengan cara yang berbeza: dalam banyak kereta lama, saluran udara masuk ke pintu, dan dalam kereta moden, kegagalan dalam kunci elektronik menghalang enjin daripada dihidupkan, yang boleh menjadi masalah semasa pembaikan.

Jadi, SRP adalah prinsip yang menerangkan CARA untuk mengurai, iaitu, di mana untuk melukis garis pemisah.

Dia mengatakan bahawa perlu untuk mengurai mengikut prinsip pembahagian "tanggungjawab," iaitu, mengikut tugas objek tertentu.

Prinsip Tanggungjawab Tunggal. Tidak semudah yang disangka

Mari kita kembali kepada minum dan kelebihan yang lelaki monyet terima semasa penguraian:

  • Kod telah menjadi sangat jelas pada setiap peringkat
  • Kod boleh ditulis oleh beberapa pengaturcara sekaligus (setiap menulis elemen berasingan)
  • Ujian automatik dipermudahkan - lebih mudah elemen, lebih mudah untuk diuji
  • Komposisi kod muncul - anda boleh menggantikannya DrinkUpOperation kepada operasi di mana seorang pemabuk menuang cecair di bawah meja. Atau gantikan operasi menuang dengan operasi di mana anda mencampurkan wain dan air atau vodka dan bir. Bergantung pada keperluan perniagaan, anda boleh melakukan segala-galanya tanpa menyentuh kod kaedah Tippler.Akta.
  • Daripada operasi ini anda boleh melipat pelahap (menggunakan sahaja TakeBitOperation), Alkohol (menggunakan sahaja DrinkUpOperation terus dari botol) dan memenuhi banyak keperluan perniagaan lain.

(Oh, nampaknya ini sudah menjadi prinsip OCP, dan saya melanggar tanggungjawab jawatan ini)

Dan, sudah tentu, keburukan:

  • Kita perlu mencipta lebih banyak jenis.
  • Seorang pemabuk minum buat pertama kalinya selepas beberapa jam daripada yang dia boleh lakukan.

Definisi 2. Kebolehubahan bersatu.

Izinkan saya, tuan-tuan! Kelas minum juga mempunyai satu tanggungjawab - ia minum! Dan secara umum, perkataan "tanggungjawab" adalah konsep yang sangat kabur. Seseorang bertanggungjawab untuk nasib manusia, dan seseorang bertanggungjawab untuk menaikkan penguin yang terbalik di tiang.

Mari kita pertimbangkan dua pelaksanaan peminum. Yang pertama, yang disebutkan di atas, mengandungi tiga kelas - tuang, minum dan snek.

Yang kedua ditulis melalui metodologi "Forward and Only Forward" dan mengandungi semua logik dalam kaedah tersebut Akta:

//НС Ρ‚Ρ€Π°Ρ‚ΡŒΡ‚Π΅ врСмя  Π½Π° ΠΈΠ·ΡƒΡ‡Π΅Π½ΠΈΠ΅ этого класса. Π›ΡƒΡ‡ΡˆΠ΅ ΡΡŠΠ΅ΡˆΡŒΡ‚Π΅ ΠΏΠ΅Ρ‡Π΅Π½ΡŒΠΊΡƒ
с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-dua kelas ini, dari sudut pandangan pemerhati luar, kelihatan betul-betul sama dan berkongsi tanggungjawab "minum" yang sama.

Kekeliruan!

Kemudian kami pergi ke dalam talian dan mengetahui definisi lain SRP - Prinsip Kebolehubah Tunggal.

SCP menyatakan bahawa "Modul mempunyai satu dan hanya satu sebab untuk berubah". Iaitu, "Tanggungjawab adalah sebab untuk perubahan."

(Nampaknya lelaki yang menghasilkan definisi asal yakin dengan kebolehan telepati lelaki beruk itu)

Sekarang semuanya jatuh ke tempatnya. Secara berasingan, kita boleh menukar prosedur menuang, minum dan snek, tetapi dalam peminum itu sendiri kita hanya boleh menukar urutan dan komposisi operasi, sebagai contoh, dengan menggerakkan snek sebelum minum atau menambah bacaan roti bakar.

Dalam pendekatan "Forward and Only Forward", semua yang boleh diubah hanya diubah dalam kaedah Akta. Ini boleh dibaca dan berkesan apabila terdapat sedikit logik dan ia jarang berubah, tetapi selalunya ia berakhir dengan kaedah yang mengerikan iaitu 500 baris setiap satu, dengan lebih banyak kenyataan-jika daripada yang diperlukan untuk Rusia menyertai NATO.

Definisi 3. Penyetempatan perubahan.

Peminum sering tidak faham mengapa mereka bangun di apartmen orang lain, atau di mana telefon bimbit mereka berada. Sudah tiba masanya untuk menambah pengelogan terperinci.

Mari kita mulakan log dengan proses menuang:

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 membungkusnya ke dalam PourOperation, kami bertindak bijak dari sudut tanggungjawab dan enkapsulasi, tetapi kini kami keliru dengan prinsip kebolehubahan. Sebagai tambahan kepada operasi itu sendiri, yang boleh berubah, pembalakan itu sendiri juga menjadi berubah. Anda perlu memisahkan dan membuat pembalak khas untuk operasi menuang:

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 teliti akan menyedarinya LogAfter, LogSebelum ΠΈ OnError juga boleh diubah secara individu, dan, dengan analogi dengan langkah sebelumnya, akan mencipta tiga kelas: PourLoggerSebelumnya, PourLoggerAfter ΠΈ PourErrorLogger.

Dan mengingati bahawa terdapat tiga operasi untuk peminum, kami mendapat sembilan kelas pembalakan. Akibatnya, keseluruhan bulatan minum terdiri daripada 14 (!!!) kelas.

Hiperbola? Sukar! Seorang lelaki monyet dengan bom tangan penguraian akan membelah "penuang" ke dalam decanter, gelas, operator menuang, perkhidmatan bekalan air, model fizikal perlanggaran molekul, dan untuk suku berikutnya dia akan cuba menguraikan kebergantungan tanpa pembolehubah global. Dan percayalah, dia tidak akan berhenti.

Pada ketika inilah ramai yang membuat kesimpulan bahawa SRP adalah cerita dongeng dari kerajaan merah jambu, dan pergi bermain mi...

... tanpa pernah belajar tentang kewujudan definisi ketiga Srp:

β€œPrinsip Tanggungjawab Tunggal menyatakan bahawa perkara yang serupa dengan perubahan hendaklah disimpan di satu tempat". atau"Apa yang berubah bersama-sama harus disimpan di satu tempat"

Iaitu, jika kita menukar pembalakan operasi, maka kita mesti mengubahnya di satu tempat.

Ini adalah perkara yang sangat penting - kerana semua penjelasan SRP di atas mengatakan bahawa perlu untuk menghancurkan jenis semasa mereka dihancurkan, iaitu, mereka mengenakan "had atas" pada saiz objek, dan kini kita sudah bercakap tentang "had rendah" . Dalam kata lain, SRP bukan sahaja memerlukan "menghancurkan sambil menghancurkan", tetapi juga tidak keterlaluan - "jangan menghancurkan perkara yang saling berkait". Ini adalah pertempuran hebat antara pisau cukur Occam dan lelaki beruk!

Prinsip Tanggungjawab Tunggal. Tidak semudah yang disangka

Sekarang peminum sepatutnya berasa lebih baik. Sebagai tambahan kepada fakta bahawa tidak perlu membahagikan pembalak IPourLogger kepada tiga kelas, kami juga boleh menggabungkan semua pembalak 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 menambah jenis operasi keempat, maka pengelogan untuknya sudah sedia. Dan kod operasi itu sendiri adalah bersih dan bebas daripada bunyi infrastruktur.

Hasilnya, kami mempunyai 5 kelas untuk menyelesaikan masalah minum:

  • Operasi menuang
  • Operasi minum
  • Operasi jamming
  • Pembalak
  • Fasad peminum

Setiap daripada mereka bertanggungjawab sepenuhnya untuk satu fungsi dan mempunyai satu sebab untuk perubahan. Semua peraturan yang serupa dengan perubahan terletak berdekatan.

Contoh kehidupan sebenar

Kami pernah menulis perkhidmatan untuk mendaftar pelanggan b2b secara automatik. Dan kaedah ALLAH muncul untuk 200 baris kandungan yang serupa:

  • Pergi ke 1C dan buat akaun
  • Dengan akaun ini, pergi ke modul pembayaran dan buatnya di sana
  • Semak bahawa akaun dengan akaun sedemikian belum dibuat pada pelayan utama
  • Buat akaun baharu
  • Tambahkan keputusan pendaftaran dalam modul pembayaran dan nombor 1c pada perkhidmatan keputusan pendaftaran
  • Tambahkan maklumat akaun pada jadual ini
  • Buat nombor mata untuk pelanggan ini dalam perkhidmatan mata. Hantar nombor akaun 1c anda kepada perkhidmatan ini.

Dan terdapat kira-kira 10 lagi operasi perniagaan dalam senarai ini dengan ketersambungan yang teruk. Hampir semua orang memerlukan objek akaun. ID titik dan nama pelanggan diperlukan dalam separuh daripada panggilan.

Selepas satu jam pemfaktoran semula, kami dapat memisahkan kod infrastruktur dan beberapa nuansa bekerja dengan akaun ke dalam kaedah/kelas yang berasingan. Kaedah Tuhan menjadikannya lebih mudah, tetapi terdapat 100 baris kod yang tinggal yang tidak mahu dirungkai.

Hanya selepas beberapa hari ia menjadi jelas bahawa intipati kaedah "ringan" ini adalah algoritma perniagaan. Dan perihalan asal spesifikasi teknikal adalah agak rumit. Dan percubaan untuk memecahkan kaedah ini menjadi kepingan yang akan melanggar SRP, dan bukan sebaliknya.

Formalisme.

Sudah tiba masanya untuk meninggalkan mabuk kita sendirian. Keringkan air mata anda - kami pasti akan kembali kepadanya suatu hari nanti. Sekarang mari kita formalkan pengetahuan daripada artikel ini.

Formalisme 1. Definisi SRP

  1. Asingkan elemen supaya setiap daripada mereka bertanggungjawab untuk satu perkara.
  2. Tanggungjawab bermaksud "sebab untuk berubah." Iaitu, setiap elemen hanya mempunyai satu sebab untuk perubahan, dari segi logik perniagaan.
  3. Potensi perubahan kepada logik perniagaan. mesti setempat. Elemen yang berubah secara serentak mestilah berdekatan.

Formalisme 2. Kriteria ujian kendiri yang diperlukan.

Saya tidak melihat kriteria yang mencukupi untuk memenuhi SRP. Tetapi ada syarat yang diperlukan:

1) Tanya diri anda apa yang kelas/kaedah/modul/perkhidmatan ini lakukan. anda mesti menjawabnya dengan definisi yang mudah. ( Terima kasih Brightori )

penerangan

Walau bagaimanapun, kadang-kadang sangat sukar untuk mencari definisi yang mudah

2) Membetulkan pepijat atau menambah ciri baharu menjejaskan bilangan fail/kelas minimum. Sebaik-baiknya - satu.

penerangan

Memandangkan tanggungjawab (untuk ciri atau pepijat) terkandung dalam satu fail/kelas, anda tahu dengan tepat di mana hendak dilihat dan perkara yang hendak diedit. Sebagai contoh: ciri menukar keluaran operasi pembalakan memerlukan perubahan hanya pembalak. Tidak perlu menjalankan seluruh kod yang lain.

Contoh lain ialah menambah kawalan UI baharu, serupa dengan yang sebelumnya. Jika ini memaksa anda menambah 10 entiti berbeza dan 15 penukar berbeza, nampaknya anda sudah keterlaluan.

3) Jika beberapa pembangun sedang mengusahakan ciri yang berbeza bagi projek anda, maka kemungkinan konflik gabungan, iaitu, kemungkinan fail/kelas yang sama akan diubah oleh beberapa pembangun pada masa yang sama, adalah minimum.

penerangan

Jika, apabila menambah operasi baru "Tuangkan vodka di bawah meja", anda perlu menjejaskan pembalak, operasi minum dan menuangkan, maka ia kelihatan seperti tanggungjawab dibahagikan secara bengkok. Sudah tentu, ini tidak selalu mungkin, tetapi kita harus cuba mengurangkan angka ini.

4) Apabila ditanya soalan yang menjelaskan tentang logik perniagaan (daripada pembangun atau pengurus), anda pergi ke satu kelas/fail dan menerima maklumat hanya dari sana.

penerangan

Ciri, peraturan atau algoritma ditulis padat, setiap satu di satu tempat, dan tidak berselerak dengan bendera di seluruh ruang kod.

5) Penamaan adalah jelas.

penerangan

Kelas atau kaedah kami bertanggungjawab untuk satu perkara, dan tanggungjawab itu ditunjukkan dalam namanya

AllManagersManagerService - kemungkinan besar kelas Tuhan
LocalPayment - mungkin tidak

Formalisme 3. Metodologi pembangunan occam-first.

Pada permulaan reka bentuk, lelaki monyet tidak tahu dan tidak merasakan semua kehalusan masalah diselesaikan dan boleh melakukan kesilapan. Anda boleh membuat kesilapan dengan cara yang berbeza:

  • Jadikan objek terlalu besar dengan menggabungkan tanggungjawab yang berbeza
  • Membingkai semula dengan membahagikan satu tanggungjawab kepada pelbagai jenis
  • Tersalah menentukan sempadan tanggungjawab

Adalah penting untuk mengingati peraturan: "lebih baik membuat kesilapan besar," atau "jika anda tidak pasti, jangan pisahkan." Jika, sebagai contoh, kelas anda mengandungi dua tanggungjawab, maka ia masih boleh difahami dan boleh dibahagikan kepada dua dengan perubahan minimum pada kod klien. Memasang kaca daripada serpihan kaca biasanya lebih sukar kerana konteks tersebar di beberapa fail dan kekurangan kebergantungan yang diperlukan dalam kod klien.

Sudah tiba masanya untuk memanggilnya sehari

Skop SRP tidak terhad kepada OOP dan SOLID. Ia terpakai kepada kaedah, fungsi, kelas, modul, perkhidmatan mikro dan perkhidmatan. Ia terpakai pada kedua-dua pembangunan "figax-figax-and-prod" dan "sains roket", menjadikan dunia lebih baik di mana-mana sahaja. Jika anda memikirkannya, ini hampir merupakan prinsip asas semua kejuruteraan. Kejuruteraan mekanikal, sistem kawalan, dan sememangnya semua sistem yang kompleks dibina daripada komponen, dan "underfragmentation" menafikan fleksibiliti pereka, "overfragmentation" menafikan kecekapan pereka, dan sempadan yang salah menghalang mereka daripada alasan dan ketenangan fikiran.

Prinsip Tanggungjawab Tunggal. Tidak semudah yang disangka

SRP tidak dicipta secara semula jadi dan bukan sebahagian daripada sains tepat. Ia keluar daripada batasan biologi dan psikologi kita. Ia hanyalah satu cara untuk mengawal dan membangunkan sistem yang kompleks menggunakan otak manusia beruk. Dia memberitahu kita bagaimana untuk mengurai sistem. Rumusan asal memerlukan banyak telepati, tetapi saya harap artikel ini membersihkan beberapa skrin asap.

Sumber: www.habr.com

Tambah komen