Ако имате нужда от повече от 3 нива на вдлъбнатини, прецакате!
„Ако имате нужда от повече от 3 нива на отстъп, така или иначе сте прецакани и трябва да поправите програмата си.“ Можете да намерите този ред в официалния стил на кодиране на ядрото на Linux от Линус Торвалдс.
Работата с дълбоко вложен код е като да се опитвате да разгадаете безкраен набор от „матрьошки“ — това е безкрайна задача. Като софтуерни инженери, наша отговорност е да напишем код, който е лесен за четене от нашите съотборници. Но понякога се натъкваме на код, който е толкова дълбоко вложен, че се чувства като чудовище, което трябва да бъде победено. Тогава е време да запретнем ръкави и да убием демона на дълбокото вмъкване чрез преработване на кода. Повярвайте ми, вашите връстници ще ви благодарят, че сте улеснили живота им.
Как? Ще обсъдя две техники за рефакторинг на дълбоко вложен код;
- Извличане
- Инверсия
Екстракция
Дълбоко вложеният код често може да е знак, че блок от код нарушава принципа на Единствена отговорностот SOLID— той прави твърде много неща и е трудно да се разбере какво се случва . В тези случаи може да бъде полезно да извлечете кодови блокове и да ги превърнете в собствени функции. Това не само ще направи вашия код по-организиран и четим, но също така ще улесни повторното му използване и поддръжка в дългосрочен план.
Нека да видим пример за извличане:
#FIXME: extract the process of finding sum def compute(x: int, y: int) -> int: if x > y: sum = 0 for num in range(y, x + 1): sum += num return sum else: return 0 def process(x: int, y: int) -> int: sum = 0 for num in range(y, x + 1): sum += num return sum def compute(x: int, y: int) -> int: if x > y: return process(x, y) #extracted function else: return 0
Инверсия
Когато кодираме, е лесно да се увлечем в манталитета на „щастливия път“ – ние се фокусираме върху успешния резултат и структурираме логиката си съответно. Но това може да доведе до дълбоко вложени кодови блокове, които са трудни за четене и разбиране. Това е мястото, където се намесва моделът на клаузата за охрана (известен още като моделът на изхвърлящия).
Като преобръщаме прогресията на нашата логика и търсим случаи, в които можем да се върнем по-рано, можем да направим нашия код по-четлив и поддържаем. С други думи, ние отхвърляме всички случаи, които не отговарят на нашите критерии за успех, и поддържаме партията (известна още като нашия код) да се движи гладко.
Нека да видим пример за инвертиране на код:
#FIXME: invert and add a guard clause def compute(x: int, y: int) -> int: if x > y: sum = 0 for num in range(y, x + 1): sum += num return sum else: return 0 def compute(x: int, y: int) -> int: if x <= y: #guard clause return 0 sum = 0 for num in range(y, x + 1): sum += num return sum
Сега нека видим какво ще се случи, ако направим и извличането, и инверсията,
def process(x: int, y: int) -> int: sum = 0 for num in range(y, x + 1): sum += num return sum def compute(x: int, y: int) -> int: if x <= y: return 0 return process(x, y)
Не изглежда ли по-добре? Процесът на рефакторинг подобри четимостта на кода и намали цикломатичната сложност.
Друг пример
Нека да видим друг пример за дълбоко вложен код и да го преработим с помощта на гореспоменатите процедури. Избирам UVa-10070 за илюстриране на примера, който е доста лесен проблем от UVa Online Judge.
проблем
10070 Високосна или невисокосна година и...
Древната раса на Гуламату е много напреднала в своята схема за изчисляване на годината. Те разбират какво е високосна година (година, която се дели на 4 и не се дели на 100, с изключение на това, че годините, които се делят на 400, също са високосна година.) и имат подобни фестивални години. Единият е фестивалът Хулукулу (случва се на години, делими се на 15) и фестивалът Булукулу (случва се на години, делими на 55, при условие че е и високосна година). За дадена година ще трябва да посочите какви свойства имат тези години. Ако годината не е високосна, нито фестивална, отпечатайте реда „Това е обикновена година“. Редът на отпечатване (ако има) на свойствата е: високосна година → huluculu → bulukulu.
Примерно въвеждане
2000 3600 4515 2001
Примерен резултат
This is leap year. This is leap year. This is huluculu festival year. This is huluculu festival year. This is an ordinary year.
Решение #1 [дълбоко вложен код]
import sys if __name__ == "__main__": first_input = True for line in sys.stdin: if not first_input: #FIXME print() else: first_input = False input_year = int(line) is_leap_year = False is_huluculu_year = False is_bulukulu_year = False if input_year % 4 == 0: #FIXME if input_year % 100 == 0: if input_year % 400 == 0: is_leap_year = True if input_year % 55 == 0: #FIXME is_bulukulu_year = True else: is_leap_year = True if input_year % 55 == 0: #FIXME is_bulukulu_year = True if input_year % 15 == 0: #FIXME is_huluculu_year = True if is_leap_year: print("This is leap year.") if is_huluculu_year: print("This is huluculu festival year.") if is_bulukulu_year: print("This is bulukulu festival year.") if not is_leap_year and not is_huluculu_year and not is_bulukulu_year: print("This is an ordinary year.")
Горният код е написан опасно дълбоко вложен умишлено.
Решение #2 [Рефакторинг]
import sys FLAG_FIRST_INPUT = True def print_blank_line_if_not_first_input(): global FLAG_FIRST_INPUT if FLAG_FIRST_INPUT: FLAG_FIRST_INPUT = False return print() def check_leap_year(year: int) -> bool: if year % 400 == 0: return True if year % 100 == 0: return False if year % 4 == 0: return True return False def check_huluculu_year(year: int) -> bool: if year % 15 == 0: return True return False def check_bulukulu_year(is_leap_year: bool, year: int) -> bool: if is_leap_year and year % 55 == 0: return True return False def process_input(year: int): is_leap_year = check_leap_year(year) is_huluculu_year = check_huluculu_year(year) is_bulukulu_year = check_bulukulu_year(is_leap_year, year) if is_leap_year: print("This is leap year.") if is_huluculu_year: print("This is huluculu festival year.") if is_bulukulu_year: print("This is bulukulu festival year.") if not is_leap_year and not is_huluculu_year and not is_bulukulu_year: print("This is an ordinary year.") if __name__ == "__main__": for line in sys.stdin: print_blank_line_if_not_first_input() process_input(int(line))
Преработих решение №1 чрез извличане на код и инверсия на кода. Ако смятате, че решение №2може да бъде написано по по-четлив начин, моля, не се колебайте да коментирате.
Want to Connect? If you have any feedback, please ping me on my LinkedIn: https://linkedin.com/in/shuhanmirza/