Jakie kroki i środki mogę podjąć, aby zapobiec głębokim wcięciom w moim kodzie?
Jakie kroki i środki mogę podjąć, aby zapobiec głębokim wcięciom w moim kodzie?
Odpowiedzi:
Głębokie wcięcie zwykle nie stanowi problemu, jeśli każda funkcja / metoda w twoim programie wykonuje jedną i tylko jedną rzecz. Czasami może być konieczne zagnieżdżenie warunków na kilku poziomach głębokości, ale mogę szczerze powiedzieć, że napisałem głęboko wcięty kod tylko kilka razy w ciągu ponad 12 lat kodowania.
Najlepsze, co możesz zrobić, to wyodrębnić metody:
int Step1(int state)
{
if (state == 100)
{
return Step2(state);
}
else
{
return Step3(state);
}
}
int Step2(int state)
{
if (state != 100)
{
throw new InvalidStateException(2, state);
}
// ....
}
if
warunków. Podsumowując, skończysz na pseudokodzie wykonywalnym.
else
bloki.
Może mógłbyś rozważyć klauzule ochronne ?
zamiast
public void DoSomething(int value){
if (someCondition){
if(someOtherCondition){
if(yetAnotherCondition){
//Finally execute some code
}
}
}
}
Robić
public void DoSomething(int value){
if(!(someCondition && someOtherCondition && yetAnotherCondition)){
return;
//Maybe throw exception if all preconditions must be true
}
//All preconditions are safe execute code
}
Jeśli kiedykolwiek będziesz miał okazję, polecam przeczytanie Code Complete autorstwa Steve'a McConnella. Ma wiele świetnych porad na te tematy.
http://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670/ref=pd_sim_b_6
Aby uzyskać więcej informacji na temat „klauzul ochronnych”, patrz: https://sourcemaking.com/refactoring/replace-nested-conditional-with-guard-clauses
Odwróć swoje if
.
Zamiast:
if (foo != null)
{
something;
something;
if (x)
{
something;
}
something;
}
else
{
boohoo;
}
Napisałbym:
if (foo == null)
{
boohoo;
return;
}
something;
something;
if (x)
{
something;
}
something;
To samo dotyczy if
- else
bloków. Jeśli else
jest krótszy / mniej zagnieżdżony, przywróć je.
Sprawdź wartości parametrów w jednym miejscu
Po wprowadzeniu metody sprawdź wszystkie parametry pod kątem niedozwolonych wartości, a następnie kontynuuj, wiedząc, że jesteś bezpieczny. To sprawia, że kod jest bardziej czytelny, ale oszczędza również później gromadzenia bloków warunkowych i rozprowadzania tych kontroli po całym podprogramie.
If
Na początku kodu, który zatrzymuje przepływ wykonywania z powodu niespełnienia niektórych warunków, znane są również jako klauzule ochronne , jak wskazał @JasonTuran. I wydaje się, że jest tak blisko, jak to możliwe, by mieć odrębne imię.
Zazwyczaj widziałem, że głęboko wcięty kod jest zwykle kodem problematycznym. Jeśli napotykasz ten problem, cofnij się i oceń, czy twoja funkcja robi zbyt wiele rzeczy.
Jednocześnie, aby odpowiedzieć na twoje pytanie, jeśli istnieje potrzeba tak głębokiego wcięcia, proponuję, abyś pozwolił, aby tam było. Z tego prostego powodu, że w takim kodzie wcięcie będzie pomocne, ponieważ prawdopodobnie będzie to bardzo długi fragment kodu.
Podziel zagnieżdżone komponenty (szczególnie te powtarzane) na osobne funkcje (jest to łatwiejsze, jeśli twój język obsługuje zamykanie) lub zamień serię zagnieżdżonych pętli na rekurencję.
Również wcięcie dwóch spacji zamiast czterech.
Nie uważam głębokich wcięć za kategoryczny problem, który należy usunąć (ani nie uważam refaktoryzacji za prawdziwą odpowiedź na wszystko).
Zazwyczaj zamiast zagnieżdżonych ifs lubię pisać logiczne instrukcje:
if (foo && bar && baz)
zamiast
if foo
if bar
if baz
Sam w to nie wierzyłem, ale według Code Complete jest to odpowiednie miejsce do użycia break
(jeśli twój zespół jest na pokładzie). Wyobrażam sobie, że jest to bardziej akceptowalne w przypadku programistów C ++, które są break
używane w switch
instrukcjach, niż w przypadku programistów Delphi, gdzie break
są używane tylko wtedy, gdy nie masz ochoty pisać while
pętli.
Wcięcie to naprawdę myśl do walki. Nauczyłem się, jak najpierw podzielić metodę na części, a następnie użyć dziwnej sztuczki, aby pominąć kolejne części, jeśli jeden element się nie powiedzie. Oto przykład :
Zamiast :
{if (networkCardIsOn() == true)
{if (PingToServer() == true)
{if (AccesLogin(login,pass) == true)
{if (nextCondition == true)
...
}
}
}
Obecnie piszę:
{vbContinue = true;
if (vbContinue) {
vbContinue = networkCardIsOn();
if (vbContinue == false) {
code to Handle This Error();
}
}
if (vbContinue) {
vbContinue = PingToServer();
if (vbContinue == false) {
code to HandleThisError2();
}
}
if (vbContinue) {
vbContinue = AccesLogin(login,pass);
if (vbContinue == false) {
HandleThisErrorToo();
}
}
...
Z początku wydaje mi się to dziwne, ale odkąd go używam, koszty utrzymania zostały podzielone na pół, a mój mózg na koniec dnia jest chłodniejszy.
W rzeczywistości korzyści wynikające z tej „techniki” polegają na tym, że złożoność kodu jest naprawdę podzielona, ponieważ kod jest mniej gęsty.
Czytając kod, nie musisz pamiętać o przeszłych warunkach: jeśli znajdujesz się w punkcie X kodu, poprzednie kroki zostały pomyślnie zakończone.
Kolejną korzyścią jest to, że „ścieżka ucieczki i warunek” od wszystkich tych zagnieżdżonych „jeśli-inaczej” jest uproszczona.