diff options
1 files changed, 45 insertions, 40 deletions
| -rw-r--r-- | src/teaching/oop-2023/week1/good-software.du | 85 |
diff --git a/src/teaching/oop-2023/week1/good-software.du b/src/teaching/oop-2023/week1/good-software.du index 3f8fd9a..bb19eb0 100644 --- a/src/teaching/oop-2023/week1/good-software.du +++ b/src/teaching/oop-2023/week1/good-software.du @@ -1,4 +1,7 @@ -# Основни добри софтуерни практики - ООП, Седмица 1, 22.02.2024 +'def title Основни добри софтуерни практики - ООП, Седмица 1, 22.02.2024 +'def desc Миналия семестър не ни стигна времето да обсъдим домашното по-сериозно. Главния проблем, който забелязах, беше колко грозно и нагласено бяха написани решенията. Ще поговорим по тази тема. + +# {title} \n @@ -6,7 +9,7 @@ Главния проблем, който забелязах, беше колко грозно и нагласено бяха написани решенията. Като въведение в ООП, ще поговорим по тази тема. -[$presentation-controller] +{presentation-controller} :title_slide @@ -18,7 +21,7 @@ ## Какво прави програмиста с чужд код? -[$br4] +{br4} .numbered 1. Чете @@ -32,17 +35,19 @@ ## Подобряване на четене -[$br4] +{br4} .unordered - Лично вярвам, че четимост е по-важна от конвенции - Човек вижда най-лесно "контрастни" шаблони ``` +'emplace . . 0.00.0 010010 815213 000000 000000 738438 . . .0000. 100001 122481 .... 0....0 011110 511119 +'endemplace Както виждате, в примера има усмихнато личице, "закодирано" по различни начини. @@ -54,7 +59,7 @@ :middle_slide -Труден за четене код [*(забелязвате ли грешките?)*]: +Труден за четене код {i (забелязвате ли грешките?)}: .font22 ``` @@ -84,7 +89,7 @@ if (board[a][b][i] == ' ') { :middle_slide -По-лесен за четене код [*(а сега виждат ли се по-лесно?)*]: +По-лесен за четене код {i (а сега виждат ли се по-лесно?)}: :font18 :code @@ -120,7 +125,7 @@ for (int i = 0; i < layers; i++) -Би трябвало да е малко по-лесно, в последния [`if`] имаме "e[=n=]ptyRow", което е печатната грешка, [`!=`] което трябва да бъде [`==`] и липсва въвеждането на [`b`] след първия [`if`]. +Би трябвало да е малко по-лесно, в последния {` if} имаме "e{b n}ptyRow", което е печатната грешка, {` !=} което трябва да бъде {` ==} и липсва въвеждането на {` b} след първия {` if}. Примерът не е перфектен, но илюстрира какво се опитвам да кажа: формата на код е от огромно значение, обръщайте ѝ внимание. @@ -130,20 +135,20 @@ for (int i = 0; i < layers; i++) ## Абстракцията - улеснява изучаване И употреба/модификация -[$br1] +{br1} .bulleted - Разделяме обработване на стойностите от директната имплементацията - Създаваме нови "машинни инструкции" -[$br1] +{br1} .bulleted -- [`doX()`] е по-лошо от [`do(X)`] +- {` doX()} е по-лошо от {` do(X)} .unordered - - можем да преизползваме [`do()`] за неща, различни от [`X`] - - един път разбрано, всяка употреба на [`do()`] също е разбрана; този стил кара [`do()`] да бъде по-обща функция, което я прави по-лесна за разбиране - - модификация на [`do(X)`] се свежда до заменяне на [`do()`] с [`doOther()`] или до заменяне на [`X`] с [`Y`]; променяме "инструкцията" или променяме данната + - можем да преизползваме {` do()} за неща, различни от {` X} + - един път разбрано, всяка употреба на {` do()} също е разбрана; този стил кара {` do()} да бъде по-обща функция, което я прави по-лесна за разбиране + - модификация на {` do(X)} се свежда до заменяне на {` do()} с {` doOther()} или до заменяне на {` X} с {` Y}; променяме "инструкцията" или променяме данната Нека сега разгледаме няколко примера, първият е много груб и елементарен, но служи за добро начало: @@ -168,14 +173,14 @@ int fib5() │ int fib(int n) Би трябвало да е супер очевидно, никой няма да пише отделна функция на Фибоначи за всяко число. -Нека сега да разгледаме няколко "реални" примера, свързани с домашното (за припомняне, условието се намира [url https://learn.fmi.uni-sofia.bg/mod/assign/view.php?id=296069 тук]). +Нека сега да разгледаме няколко "реални" примера, свързани с домашното (за припомняне, условието се намира {url тук;https://learn.fmi.uni-sofia.bg/mod/assign/view.php?id=296069}). Самите решения няма винаги да са добре форматирани, това е направено с цел да се поберат добре в слайда. Първия е върху функция, която приема координатите на плочка, премахва я от дъската (ако съществува) и я слага в края на празния ред. :slide -Лошо - [`doX()`] в тялото на функцията +Лошо - {` doX()} в тялото на функцията ```cpp void tryTakeTile(char** board, int rows, int cols, @@ -201,16 +206,16 @@ void tryTakeTile(char** board, int rows, int cols, Повечето предадени работи са имплементирали функционалността по точно този начин. Замислете се обаче, какво става ако сменим типа данна, тоест ако дъската е триизмерна? -Тогава трябва да добавим още една [`int`] променлива, още едно условие в [`if`], трябва да я добавим при трите индексирания на [`board`]. +Тогава трябва да добавим още една {` int} променлива, още едно условие в {` if}, трябва да я добавим при трите индексирания на {` board}. -Какво става ако променим самата структура от данни за [`emptyRow`] (за сега сте учили само масиви, но скоро ще видите, че има и други начини да съхраняваш редица информация), тогава ще трябва да пренапишем целия цикъл. +Какво става ако променим самата структура от данни за {` emptyRow} (за сега сте учили само масиви, но скоро ще видите, че има и други начини да съхраняваш редица информация), тогава ще трябва да пренапишем целия цикъл. Тоест, ако променим нещо, трябва да пренапишем значителни части от самия код, и като разширим това върху 10-20 функции, изведнъж малка промяна става голяма. Нека сега да пренапишем функцията, използвайки колкото се може повече абстракция. :middle_slide -Добро - [`do(X)`] +Добро - {` do(X)} :code_cpp bool tryReadCoordinates(int& x, int& y) { @@ -233,9 +238,9 @@ void tryTakeTile(char** board, int rows, int cols, -Искаме трето измерение на [`board`]? -Няма проблем, написали сме нова [`tryReadCoordinates`], която приема три променливи, и в [`tryTakeTile`] добавяме една декларация и обновяваме двете индексирания на [`board`]. -Ако [`emptyRow`] стане друг тип данна, просто променяме функцията да бъде [`appendToOtherDataType`] и сме готови. +Искаме трето измерение на {` board}? +Няма проблем, написали сме нова {` tryReadCoordinates}, която приема три променливи, и в {` tryTakeTile} добавяме една декларация и обновяваме двете индексирания на {` board}. +Ако {` emptyRow} стане друг тип данна, просто променяме функцията да бъде {` appendToOtherDataType} и сме готови. И ако тази промяна трябва да се направи в много функции, даже бихме могли да употребяваме функционалност на самия текстов редактор да търси и заменя срещания на един низ с друг. @@ -244,7 +249,7 @@ void tryTakeTile(char** board, int rows, int cols, :slide -Лошо - всички цикли са [`doX()`] +Лошо - всички цикли са {` doX()} ```cpp void generateLayer(char** layer, int rows, int cols, @@ -272,15 +277,15 @@ void generateLayer(char** layer, int rows, int cols, Този сегмент е по-интересен за променяне от преди, понеже за да го направим хубаво трябва да променим самата ни логика. Запълването със шпации е ясно, обаче случайното поставяне на плочки не е. -Ако имаме функция [`placeRandomЕлемент`] която само поставя една плочка в слоя, пак ще имаме вложени цикли, трябва да има начин да останем с един цикъл. -Ако направим [`placeRandomElementNTimes`], тогава пък абстрахираната функция става твърде специфична и вече имаме [`doX()`] при самата нея. +Ако имаме функция {` placeRandomЕлемент} която само поставя една плочка в слоя, пак ще имаме вложени цикли, трябва да има начин да останем с един цикъл. +Ако направим {` placeRandomElementNTimes}, тогава пък абстрахираната функция става твърде специфична и вече имаме {` doX()} при самата нея. Чрез функционално програмиране този проблем може да се разреши лесно, обаче вие ще го учите следващия семестър. С достатъчно размишления, може да стигнем до следния вариант: вместо всяка плочка да я поставяме на случайна позиция, защо не поставим всички плочки една след друга и не разбъркаме слоя? :middle_slide -Добро - [`do(X)`] +Добро - {` do(X)} ```cpp void generateLayer(char** layer, int rows, int cols, @@ -296,7 +301,7 @@ void generateLayer(char** layer, int rows, int cols, shuffleMatrix(layer, rows, cols); } -[=Бонус:=] работи по-коректно от преди +{b Бонус:} работи по-коректно от преди В предходния вариант, ако примерно цялата е пълна освен една клетка, и трябва да поставим една финална плочка, тогава трябва да чакаме докато случайните стойности се напаснат точно с координатите на тази една клетка. @@ -304,14 +309,14 @@ void generateLayer(char** layer, int rows, int cols, Даже, този проблем става все по-тегав с времето: с всяка запълнена клетка, шанса случайните стойности да ударят на заети координати се увеличава. Докато тук, нямаме този проблем, отнема някакво консистентно предопределено време да разбъркаме матрицата. -Как имплементираме [`shuffleMatrix`] си има и своите тънкости, но най-елементарния начин е просто да направим сортираш алгоритъм, при който вместо да сравним две стойности, използваме случайна стойност, която определя дали ще разменим плочките. +Как имплементираме {` shuffleMatrix} си има и своите тънкости, но най-елементарния начин е просто да направим сортираш алгоритъм, при който вместо да сравним две стойности, използваме случайна стойност, която определя дали ще разменим плочките. Друго хубаво нещо на решения с абстракция, е един лек лавинен ефект, където веднъж като почнем да мислим за абстракции, намираме къде да ги ползваме навсякъде. Да разгледаме същата функция, обаче при D подточка. :middle_slide -Решения със слоеве правят същото. Лошо - [`doX()`] при циклите: +Решения със слоеве правят същото. Лошо - {` doX()} при циклите: ```cpp void generate(char*** board, int rows, int cols, int layers, @@ -325,13 +330,13 @@ void generate(char*** board, int rows, int cols, int layers, } -Без абстракция отново ще пренапишем голяма част от [`generate`] функцията, за да се побере на слайда съм оставил само "най-сложната" промяна - случайните стойности. -Пак, имаше решения на подточката, без преизползване на [`generate`] за един слой. -Айде да разгледаме решението, заедно с абстрахираната [`generate`]. +Без абстракция отново ще пренапишем голяма част от {` generate} функцията, за да се побере на слайда съм оставил само "най-сложната" промяна - случайните стойности. +Пак, имаше решения на подточката, без преизползване на {` generate} за един слой. +Айде да разгледаме решението, заедно с абстрахираната {` generate}. :slide -Добро - [`do(X)`] +Добро - {` do(X)} ```cpp void generateLayer(char** layer, int rows, int cols, @@ -357,14 +362,14 @@ void generate(char*** board, int rows, int cols, int layers, Супер елементарно, има-няма 5-6 реда код промяна, при генериране за всеки слой, просто извикваме функцията за генериране върху един слой, за всеки слой. А в самата функция само премахваме броя вмъкнати плочки от сумата на даден тип. -Наблюдателните са забелязали, че в "ръчното" решение слоевете са третия индекс, тоест имаме [`board[x][y][layer]`], докато в това решение е първия, тоест [`board[layer][x][y]`]. +Наблюдателните са забелязали, че в "ръчното" решение слоевете са третия индекс, тоест имаме {` board[x][y][layer]}, докато в това решение е първия, тоест {` board[layer][x][y]}. Разликата е значителна, но от гледна точка на код, особено когато използваме абстракция, промяната е лесна. Иначе, в момента се намираме в много добра позиция, без абстракция как правим рестартирането на играта? По условие броя и типовете плочки трябва да се запазят, но позиции не. Истината е, че става малко тромаво: цикълът за намираме на позиции се преизползва, и броя плочки трябва да се запази. -Обаче, сега това става с едно [`shuffleMatrix`], не е нужно нищо специално да правим! +Обаче, сега това става с едно {` shuffleMatrix}, не е нужно нищо специално да правим! :middle_slide35 @@ -372,7 +377,7 @@ void generate(char*** board, int rows, int cols, int layers, .unordered - Целта на коментарите е да направи разбиране и модификация на код по-лесни. -- В повечето случаи, коментарът трябва да ни дава [*нова*] информация или да [*свърже*] парчета информация +- В повечето случаи, коментарът трябва да ни дава {i нова} информация или да {i свърже} парчета информация Да разгледаме следния пример. @@ -401,7 +406,7 @@ for (int i = 0; i < rows; i++) { } -При добрия вариант, получаваме нова информация, че това запълване се случва понеже ще подаваме към някаква си [`functionX`], и че евентуално може да има други функции, при които празни клетки може да не се обозначават с [`'\0'`]. +При добрия вариант, получаваме нова информация, че това запълване се случва понеже ще подаваме към някаква си {` functionX}, и че евентуално може да има други функции, при които празни клетки може да не се обозначават с {` '\0'}. В лошия можем да стигнем до същото заключение като погледнем самия код. :comment @@ -412,9 +417,9 @@ for (int i = 0; i < rows; i++) { .quote For practical reasons, the demonstration programs must be small, many times smaller than the "life-size programs" I have in mind. My basic problem is that precisely this difference in scale is one of the major sources of our difficulties in programming! -— Edsger W. Dijkstra, [url https://www.cs.utexas.edu/users/EWD/ewd02xx/EWD249.PDF Notes on Structured Programming, On our inability to do much] +— Edsger W. Dijkstra, {url Notes on Structured Programming, On our inability to do much;https://www.cs.utexas.edu/users/EWD/ewd02xx/EWD249.PDF} -[$br1] +{br1} Решение на цялото домашно, с описаните методи TODO @@ -424,13 +429,13 @@ for (int i = 0; i < rows; i++) { -Умението да разрешиш един проблем [*не те*] прави добър програмист, да го разрешиш по четим, разширяем, лесен, ефикасен, ... начин [*те*] прави добър програмист. +Умението да разрешиш един проблем {i не те} прави добър програмист, да го разрешиш по четим, разширяем, лесен, ефикасен, ... начин {i те} прави добър програмист. :slide35 ## Изводи -[$br4] +{br4} .unordered - Форматирайте/подравнявайте си кода, искате лесно четене |