Zasady SOLID w Pythonie dla początkujących.

python solid

W wielu branżach obowiązują ogólnie przyjęte dobre praktyki, które ułatwiają wykonywać dany zawód. Także w programowaniu. W tym wpisie skupię się na zasadach SOLID (na przykładach w Pythonie), które definiują sprawdzone sposoby programowania obiektowego.

Czym są zasady SOLID?

SOLID jest to zbiór 5 podstawowych zasad, które służą jako drogowskaz przy tworzeniu kodu obiektowego. Dzięki nim kod jest łatwiejszy do zrozumienia, rozwijania oraz utrzymywania. Ich autorem jest Robert C. Martin, który opracował je w swojej pracy w 2000. roku „Design Principles and Design Patterns” (LINK). Co ciekawe, skrótowiec SOLID do tych zasad został wprowadzony później przez innego zasłużonego inżyniera oprogramowania Micheala Feathersa.

SOLID to:

SOLID znajduje również zastosowanie w Pythonie, który wspiera min. paradygmat programowania obiektowego. W tym artykule opiszę pierwszą zasadę – SRP – na przykładach, a w kolejnych zajmę się rozwinięciem następnych reguł.

The Single Responsibility Principle (Python SOLID)

Zasada pierwotnie opracowana i opublikowana przez Toma DeMarco wraz z Meilir Page-Jonesem w książce „Practical Guide to Structured Systems Design” (LINK).

Mówi o tym, że klasa powinna mieć dokładnie jeden powód aby zostać zmodyfikowana, nie więcej. Czyli w celu ograniczenia „powodów” do modyfikacji, należy ograniczyć odpowiedzialność danej klasy do jednej. Złą praktyką są więc klasy-kombajny robiące „wszystko”.

Dlaczego tak ważne jest rozdzielanie odpowiedzialności do oddzielnych klas? Ponieważ każda funkcjonalność to powód do zmiany, kiedy zmieniają się wymagania. Jeśli klasa ma więcej niż jedną odpowiedzialność, wtedy ma więcej niż jeden powód do zmiany. Odpowiedzialności stają się połączone(ang. coupled). Zmiany w jednej mogą zaszkodzić/popsuć funkcjonowanie drugiej. Tego typu powiązanie prowadzi do słabych projektów, które nieoczekiwanie przestają działać, kiedy się je zmodyfikuje.

Klasyczny przykład

Załóżmy, że posiadamy następującą implementację klasy Employee:

class Employee():
    def calculate_payment(self):
        ...

    def report_hours(self):
        ...

    def save(self):
        ...

Klasa Employee zawiera trzy metody, które dotyczą trzech różnych potrzeb biznesowych: kalkulacje płac dotyczą finansów, raportowanie godzin dotyczy działu operacyjnego, a zapisanie obiektu do bazy danych to kwestia techniczna.

Nie chcemy, aby na przykład z powodu modyfikacji metody report_hours dwie pozostałe funkcjonalności z innych obszarów biznesowych przestały działać. To zaburza zasadę SRP.

Wyodrębnijmy więc funkcjonalności do osobnych klas tak, aby zniwelować ich wzajemny wpływ na siebie:

class EmployeePaymentCalculator():
    def calculate_payment(self, employee):
        ...


class EmployeeTimeReport():
    def report_hours(self, employee):
        ...


class EmployeeRepository():
    def save(self, employee):
        ...

czym jest odpowiedzialność?

Jak wspomniałem wcześniej, w SRP odpowiedzialność to tak naprawdę powód do zmiany. Czasami trudno jest je zauważyć, ponieważ jesteśmy przyzwyczajeni do myślenia o funkcjonalnościach w grupach. Pokażę to Tobie na przykładzie implementacji modemu z książki „Agile Principles, Patterns, and Practices in C#” (LINK).

class Modem():
  def dial(pno):
      ...

  def hangup():
      ...

  def send(s):
      ...

  def recv():
      ...

Na pierwszy rzut oka te cztery metody wyglądają jak najbardziej w porządku. Aczkolwiek mamy tutaj dwie odpowiedzialności, jedna do zarządzania połączeniem(dial, hangup), a druga do komunikacji(send, recv).

Możemy rozdzielić tę implementację na zarządzającą połączeniem oraz zarządzającą komunikacją. Aczkolwiek jeśli wiemy, że obie te funkcjonalności mogą być modyfikowane z tego samego powodu(w tym przypadku to powód techniczny), wtedy nie musimy ich rozdzielać. Właściwie wtedy byłoby to niepotrzebną komplikacją kodu.

Srp w pythonie – warto!

SRP to fundamentalna zasada tworzenia elastycznego kodu obiektowego. Przeważnie na początku pisania kodu, jest on dobrze podzielony przez autora. Dopiero z czasem, gdy kolejni developerzy dopisują kolejne funkcjonalności łamane do już istniejących klas. Poprzez to „puchną” i często tworzą się szkodliwe połączenia(ang. coupling) pomiędzy nimi. Utrudnia to znacznie pracę nad kodem.

Grupujmy elementy jeśli mogą zmienić się z tego samego powodu, rozdzielajmy te które mogą się zmienić z różnych powodów.

Robert C. Martin

W powyższym artykule skupiałem się na opisywaniu SRP w kontekście klas, aczkolwiek dobrą praktyką jest stosować je także do modułów, metod i interfejsów.

Warto więc stosować się do SRP, szczególnie zwracając uwagę na to wtedy, gdy dodajemy jakąś funkcjonalność do już istniejącej aplikacji.


Bądź na bieżąco z blogiem!

Bądź na bieżąco z kolejnymi metariałami publikowanymi na tym blogu i innymi użytecznymi (moim zdaniem) materiałami zapisując się do newslettera 🙂

Jako prezent otrzymasz ode mnie mini e-book „Jak zacząć działać w Open Source i rozwijać swoje umiejętności” 🙂