Co oznacza „wysoka spójność”?

28

Jestem studentem, który niedawno dołączył do firmy programistycznej jako stażysta. Na uniwersytecie jeden z moich profesorów mówił, że musimy dążyć do osiągnięcia „niskiego sprzężenia i wysokiej spójności”.

Rozumiem znaczenie niskiego sprzężenia. Oznacza to zachowanie kodu oddzielnych komponentów osobno, aby zmiana w jednym miejscu nie złamała kodu w innym.

Ale co oznacza wysoka spójność. Jeśli oznacza to integrację różnych elementów tego samego elementu ze sobą, nie rozumiem, w jaki sposób staje się to korzystne.

Co oznacza wysoka spójność? Czy można wyjaśnić przykład, aby zrozumieć jego zalety?

Max
źródło
1
Czy artykuł w Wikipedii nie odpowiada wystarczająco na twoje pytanie? en.wikipedia.org/wiki/Coicity_(computer_science)
Eoin Carroll
Dobry artykuł na ten temat znajduje się na stronie: msdn.microsoft.com/en-us/magazine/cc947917.aspx
NoChance
1
@EoinCarroll: Niestety artykuł w Wikipedii w tej chwili nie zawiera żadnych dobrych konkretnych przykładów, z którymi mogliby współpracować nowi programiści. Teoria jest dobra i wszystko, ale tak naprawdę się nie trzyma, dopóki nie popełnisz błędów związanych z niską spójnością. Wysoka spójność jest jednym z tych tematów, które zajęły mi kilka lat programowania, aby w pełni zrozumieć, dlaczego jest to ważne i jak to osiągnąć.
Spoike,
Nigdy tak naprawdę nie rozumiałem spójności, dopóki nie przeczytałem Clean Code. Ty też powinieneś.
Sebastian Redl,

Odpowiedzi:

25

Jednym ze sposobów spojrzenia na spójność w kategoriach OO jest to, czy metody w klasie używają któregokolwiek z prywatnych atrybutów. Korzystając z takich wskaźników, jak LCOM4 (brak spójnych metod), jak zauważył gnat w tej odpowiedzi tutaj , możesz zidentyfikować klasy, które można by refaktoryzować. Powodem, dla którego chcesz zmienić metody lub klasy, aby były bardziej spójne, jest to, że upraszcza to projektowanie kodu innym użytkownikom . Zaufaj mi; większość specjalistów technologicznych i programistów zajmujących się konserwacją pokocha Cię, gdy naprawisz te problemy.

Możesz użyć narzędzi w procesie kompilacji, takich jak Sonar, aby zidentyfikować niską spójność w bazie kodu. Istnieje kilka bardzo częstych przypadków, które mogę wymyślić, gdzie metody mają niską „spójność” :

Przypadek 1: Metoda w ogóle nie jest powiązana z klasą

Rozważ następujący przykład:

public class Food {
   private int _foodValue = 10;

   public void Eat() {
     _foodValue -= 1;
   }

   public void Replenish() {
     _foodValue += 1;
   }

   public void Discharge() {
     Console.WriteLine("Nnngghhh!");
   }
}

Jednej z metod Discharge()brakuje spójności, ponieważ nie dotyka ona żadnego z prywatnych członków klasy. W tym przypadku istnieje tylko jeden prywatny użytkownik: _foodValue. Jeśli nie ma to nic wspólnego z wewnętrznymi elementami klasy, to czy naprawdę tam jest? Metodę można przenieść do innej klasy, którą można nazwać np FoodDischarger.

// Non-cohesive function extracted to another class, which can
// be potentially reused in other contexts
public FoodDischarger {
  public void Discharge() {
    Console.WriteLine("Nnngghhh!");
  }
}

Robiąc to w JavaScript, ponieważ funkcje są obiektami najwyższej klasy, zrzut może być funkcją bezpłatną:

function Food() {
    this._foodValue = 10;
}
Food.prototype.eat = function() {
    this._foodValue -= 1;
};
Food.prototype.replenish = function() {
    this._foodValue += 1;
};

// This
Food.prototype.discharge = function() {
    console.log('Nnngghhh!');
};
// can easily be refactored to:
var discharge = function() {
    console.log('Nnngghhh!');
};
// making it easily reusable without creating a class

Przypadek 2: Klasa użyteczności

Jest to w rzeczywistości częsty przypadek, który niszczy spójność. Wszyscy uwielbiają klasy użytkowe, ale zwykle wskazują one na wady projektowe i przez większość czasu baza kodu jest trudniejsza do utrzymania (ze względu na wysoką zależność związaną z klasami użyteczności). Rozważ następujące klasy:

public class Food {
    public int FoodValue { get; set; }
}

public static class FoodHelper {

    public static void EatFood(Food food) {
        food.FoodValue -= 1;
    }

    public static void ReplenishFood(Food food) {
        food.FoodValue += 1;
    }

}

Tutaj widzimy, że klasa narzędziowa musi mieć dostęp do właściwości w klasie Food. Metody w klasie użyteczności nie mają w tym przypadku żadnej spójności, ponieważ do wykonania swojej pracy potrzebne są zasoby zewnętrzne. Czy w takim przypadku nie byłoby lepiej mieć metody w klasie, w której pracują same (podobnie jak w pierwszym przypadku)?

Przypadek 2b: Ukryte obiekty w klasach użytkowych

Istnieje inny przypadek klas narzędzi, w których znajdują się niezrealizowane obiekty domeny. Pierwszą reakcją szarpiącego kolana, jaką programista ma podczas programowania manipulacji łańcuchem, jest napisanie dla niego klasy użyteczności. Podobnie jak tutaj, który potwierdza kilka typowych reprezentacji ciągów:

public static class StringUtils {

  public static bool ValidateZipCode(string zipcode) {
    // validation logic
  }

  public static bool ValidatePhoneNumber(string phoneNumber) {
    // validation logic
  }

}

To, czego większość nie zdaje sobie sprawy, to to, że kod pocztowy, numer telefonu lub jakakolwiek inna reprezentacja ciągu może być samym obiektem:

public class ZipCode {
    private string _zipCode;
    public bool Validates() {
      // validation logic for _zipCode
    }
}

public class PhoneNumber {
    private string _phoneNumber;
    public bool Validates() {
      // validation logic for _phoneNumber
    }
}

Pojęcie, że nie powinieneś „bezpośrednio obsługiwać ciągów” jest szczegółowo opisane w tym blogu przez @codemonkeyism , ale jest ściśle związane ze spójnością, ponieważ sposób, w jaki programiści używają ciągów, umieszczając logikę w klasach narzędzi.

Łup
źródło
Teraz, gdybyśmy tylko mogli uzyskać ORM do prawidłowej obsługi naszych niestandardowych klas ZipCode i PhoneNumber: |
Pete
+1 dobre przykłady. Ostatni punkt znajduje się również na stronie sourcemaking.com/refactoring/primitive-obsession
AlexFoxGill
@Pete Twój ORM zajmie się tym, jeśli podasz operatory konwersji z ciągu na zdefiniowany typ (ZipCode, PhoneNumber): msdn.microsoft.com/en-us/library/85w54y0a.aspx
Marcus
9

Wysoka spójność oznacza trzymanie razem podobnych i powiązanych rzeczy, łączenie lub łączenie części, które mają wspólną treść, funkcjonalność, przyczynę lub cel . Innymi słowy, niska spójność może na przykład oznaczać encję funkcji / klasy / kodu, która służy wielu celom, a nie „ do rzeczy ”. Jednym z pomysłów jest zrobienie jednej rzeczy i zrobienie tego dobrze . Inne mogą obejmować oczywisty fakt, że w wielu miejscach nie powielasz podobnej funkcjonalności. Poprawia to także lokalizację bazy kodu, pewne rzeczy znajdują się w określonym miejscu (plik, klasa, zestaw funkcji, ...), a nie są rozproszone.

Jako przykład rozważ klasę, która służy dwóm lub trzem celom: Ładuje / przechowuje zasoby (na przykład plik), a następnie analizuje i wyświetla zawartość. Taka klasa ma niską spójność, ponieważ zarządza co najmniej dwoma oddzielnymi zadaniami, które w ogóle nie są powiązane (we / wy pliku, analiza i wyświetlanie). Projekt o wysokiej spójności mógłby wykorzystywać odrębne klasy do ładowania i przechowywania zasobu, analizowania go, a następnie wyświetlania.

Z drugiej strony, niskie sprzężenie ma na celu zachowanie oddzielnych elementów - tak, aby oddziaływały ze sobą jak najmniej, co następnie zmniejsza złożoność i upraszcza projekt.

zxcdw
źródło
7

Oznacza to, że części danego obiektu są ściśle związane z funkcją obiektu. Oznacza to, że w obiekcie jest bardzo mało odpadów lub nie ma ich wcale pod względem funkcji lub odpowiedzialności. To z kolei może lepiej zrozumieć, do czego dany obiekt ma być używany.

Inżynier świata
źródło
czy nie dotyczy to także wyższego poziomu niż tylko obiektów? np. grupowanie obiektów / funkcji związanych z zadaniem w przestrzeni nazw?
stijn