Niedawno odziedziczyłem bazę kodów, w której znajdują się główni naruszający Liskov. W ważnych klasach. Sprawiło mi to ogromny ból. Pozwól mi wyjaśnić dlaczego.
Mam Class A
, co wynika z Class B
. Class A
i Class B
udostępniamy kilka właściwości, które Class A
zastępują własną implementację. Ustawienie lub uzyskanie Class A
właściwości ma inny efekt niż ustawienie lub pobranie dokładnie tej samej właściwości Class B
.
public Class A
{
public virtual string Name
{
get; set;
}
}
Class B : A
{
public override string Name
{
get
{
return TranslateName(base.Name);
}
set
{
base.Name = value;
FunctionWithSideEffects();
}
}
}
Pomijając fakt, że jest to bardzo okropny sposób na tłumaczenie w .NET, istnieje wiele innych problemów z tym kodem.
W tym przypadku Name
jest używany jako indeks i zmienna kontroli przepływu w wielu miejscach. Powyższe klasy są zaśmiecone w całej bazie kodu zarówno w postaci surowej, jak i pochodnej. Naruszenie zasady podstawienia Liskowa w tym przypadku oznacza, że muszę znać kontekst każdego pojedynczego wywołania każdej z funkcji zajmujących klasę podstawową.
Zastosowania kodów obiektów zarówno Class A
a Class B
, więc nie mogę po prostu zrobić Class A
abstrakcyjny, aby zmusić ludzi do używania Class B
.
Istnieje kilka bardzo przydatnych funkcji narzędziowych, które działają Class A
i inne bardzo użytecznych funkcji narzędziowych, które działają Class B
. Idealnie chciałabym, aby móc korzystać z żadnej funkcji użytkowej, która może działać na Class A
na Class B
. Wiele funkcji, które podejmują, Class B
może łatwo przyjąć, Class A
gdyby nie naruszenie LSP.
Najgorsze w tym jest to, że ten konkretny przypadek jest naprawdę trudny do refaktoryzacji, ponieważ cała aplikacja opiera się na tych dwóch klasach, działa przez cały czas na obu klasach i pękłaby na sto sposobów, jeśli to zmienię (co zamierzam zrobić tak czy inaczej).
Będę musiał to naprawić, aby utworzyć NameTranslated
właściwość, która będzie Class B
wersją Name
właściwości i bardzo, bardzo ostrożnie zmieniać każde odwołanie do Name
właściwości pochodnej, aby użyć mojej nowej NameTranslated
właściwości. Jednak błędne podanie choćby jednego z tych odniesień może spowodować wysadzenie całej aplikacji.
Biorąc pod uwagę, że podstawa kodu nie ma wokół siebie testów jednostkowych, jest to prawie najbardziej niebezpieczny scenariusz, z którym może spotkać się programista. Jeśli nie zmienię naruszenia, muszę wydać ogromne ilości energii mentalnej na śledzenie, na jakim typie obiektu działa każda metoda, a jeśli naprawię naruszenie, mógłbym spowodować, że cały produkt wybuchnie w nieodpowiednim czasie.