Piramida zagłady (programowanie)
W programowaniu komputerowym piramida zagłady jest częstym problemem, który pojawia się, gdy program wykorzystuje wiele poziomów zagnieżdżonych wcięć do kontrolowania dostępu do funkcji. Jest to często spotykane podczas sprawdzania pustych wskaźników lub obsługi wywołań zwrotnych . Dwa przykłady tego terminu dotyczą określonego stylu programowania w JavaScript oraz zagnieżdżania instrukcji if , które występują w obiektowych językach programowania, gdy jeden z obiektów może być wskaźnikiem zerowym.
Przykłady
Większość nowoczesnych obiektowych języków programowania używa stylu kodowania znanego jako notacja kropkowa, która umożliwia zapisanie wielu wywołań metod w jednym wierszu kodu, każde wywołanie jest oddzielone kropką. Na przykład:
theWidth = windows ( "Główny" ). widoki ( 5 ). rozmiar (). szerokość ();
Ten kod zawiera cztery różne instrukcje; najpierw szuka w kolekcji okien okna o nazwie „Main”, następnie szuka w kolekcji widoków tego okna piątego widoku podrzędnego, następnie wywołuje metodę size, aby zwrócić strukturę z wymiarami widoku, a na końcu wywołuje
metodę width
na tej strukturze, aby wygenerować wynik, który jest przypisany do zmiennej o nazwie theWidth
.
Problem z tym podejściem polega na tym, że kod zakłada, że istnieją wszystkie te wartości. Chociaż rozsądne jest oczekiwanie, że okno będzie miało rozmiar, a ten rozmiar będzie miał szerokość, w ogóle nie jest rozsądne zakładanie, że okno o nazwie „Główne” będzie istnieć ani że ma pięć widoków podrzędnych. Jeśli którekolwiek z tych założeń jest błędne, jedna z metod zostanie wywołana z wartością null, powodując błąd wskaźnika zerowego.
Aby uniknąć tego błędu, programista musi sprawdzić każde wywołanie metody, aby upewnić się, że zwraca wartość. Bezpieczniejszą wersją tego samego kodu byłoby:
jeśli okna . zawiera ( "Main" ) { if windows ( "Main" ). widoki . zawiera ( 5 ) { theWidth = windows ( "Main" ). widoki ( 5 ). rozmiar (). szerokość (); //więcej kodu, który działa zWidth } }
Jeśli programista chce użyć tej wartości w zależności od tego, czy istnieje i jest poprawna, kod funkcjonalny wewnątrz instrukcji if
jest przesunięty w prawo, co utrudnia czytanie dłuższych wierszy. Prowadzi to często do prób „spłaszczenia” kodu:
jeśli okna . zawiera ( "Main" ) { theWindow = windows ( "Main" ) } if theWindow != null && theWindow . widoki . zawiera ( 5 ) { Widok = Okno . odsłon ( 5 ) } if theView != null {
szerokość = widok . rozmiar (). szerokość (); //dodatkowy kod }
Lub alternatywnie:
jeśli ! okna . zawiera ( "Main" ) { //obsługa błędu } else if ! okna ( "Główne" ). widoki . zawiera ( 5 ) { // obsługa błędu } else { theWidth = windows ( "Main" ). widoki ( 5 ). rozmiar (). szerokość
(); //więcej kodu, który działa zWidth }
Ten rodzaj konstrukcji programistycznej jest bardzo powszechny, a wiele języków programowania dodało pewien rodzaj cukru składniowego , aby temu zaradzić. Na przykład Swift firmy Apple dodał koncepcję opcjonalnego łączenia instrukcji if, podczas gdy Microsoft C# 6.0 i Visual Basic 14 dodały operatory warunkowe o wartości null ?.
i ?[
odpowiednio dla dostępu do członków i indeksowania. Podstawową ideą jest umożliwienie ciągowi wywołań metod natychmiastowego zwracania wartości null, jeśli którykolwiek z jego elementów ma wartość null, na przykład:
theWidth = windows ( "Główny" ) ? . wyświetlenia ( 5 ) ? . rozmiar . szerokość ;
przypisałby null do theWidth
, jeśli brakuje „Głównego” lub piątego widoku podrzędnego, lub uzupełni instrukcję i zwróci szerokość, jeśli oba są prawidłowe. Istnieje wiele sytuacji, w których programista chce podjąć różne działania w tych dwóch przypadkach, więc Swift dodaje inną formę cukru składniowego do tej roli, instrukcję if let
, znaną również jako „opcjonalne wiązanie”:
jeśli pozwolimy theView = windows ( "Main" ) ? . views ( 5 ) { //rób rzeczy wiedząc, że widok istnieje... theWidth = theView . rozmiar . szerokość }
Rezolucja
Pyramid of Doom można zwykle rozwiązać w dowolnym języku, po prostu dzieląc kod na wiele zagnieżdżonych funkcji (lub innych grup). Na przykład zamiast:
main () { aaaaa () { bbbbb () { ccccc () { ddddd () { // zrób coś teraz } } } } }
Możesz rozbić funkcjonalność w następujący sposób:
zrób coś () { // zrób coś teraz } CC () { ccccc () { ddddd () { zrób coś () } } } main () { aaaaa () { bbbbb () { CC () } } }
Podobnie struktury danych można podzielić na poziomy, gdy występują podobne piramidy.
Piramida Zagłady nie tylko została rozwiązana, ale lepiej jest nie mieć dużych, skomplikowanych funkcji; mniejsze są łatwiejsze do uzyskania, a także łatwiejsze do odczytania i zweryfikowania działania. Wybór nazw funkcji dla każdego z tych poziomów pomoże również autorowi wyjaśnić czytelnikom, co i gdzie się robi. Zazwyczaj każdy poziom nie wymaga wielu połączeń z odległymi poziomami, więc oddzielenie ich jest łatwe. Jeśli istnieją takie powiązania, autor może przemyśleć swój projekt na coś bardziej niezawodnego, ponieważ jest to żyzne źródło błędów.
Zobacz też
- Obietnice , technika unikania piramidy zagłady, stosowana np. w JavaScript
- Prawo Demeter
- Operator bezpiecznej nawigacji , operator języka programowania, który pozwala uniknąć piramidy zagłady