Summary Files Commits Branches @commit @diff @stats
path: root / src / teaching / oop-2023 / week1 / good-software.du
diff options
context:
space:
mode:
1 files changed, 47 insertions, 40 deletions
-rw-r--r--src/teaching/oop-2023/week1/good-software.du87
diff --git a/src/teaching/oop-2023/week1/good-software.du b/src/teaching/oop-2023/week1/good-software.du
index 3f8fd9a..237cec2 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,16 +309,17 @@ void generateLayer(char** layer, int rows, int cols,
Даже, този проблем става все по-тегав с времето: с всяка запълнена клетка, шанса случайните стойности да ударят на заети координати се увеличава.
Докато тук, нямаме този проблем, отнема някакво консистентно предопределено време да разбъркаме матрицата.
-Как имплементираме [`shuffleMatrix`] си има и своите тънкости, но най-елементарния начин е просто да направим сортираш алгоритъм, при който вместо да сравним две стойности, използваме случайна стойност, която определя дали ще разменим плочките.
+Как имплементираме {` shuffleMatrix} си има и своите тънкости, но най-елементарния начин е просто да направим сортираш алгоритъм, при който вместо да сравним две стойности, използваме случайна стойност, която определя дали ще разменим плочките.
Друго хубаво нещо на решения с абстракция, е един лек лавинен ефект, където веднъж като почнем да мислим за абстракции, намираме къде да ги ползваме навсякъде.
Да разгледаме същата функция, обаче при D подточка.
:middle_slide
-Решения със слоеве правят същото. Лошо - [`doX()`] при циклите:
+Решения със слоеве правят същото. Лошо - {` doX()} при циклите:
```cpp
+'emplace
void generate(char*** board, int rows, int cols, int layers,
char* tiles, int* tileCounts, int tilesSize) {
...
@@ -323,15 +329,16 @@ void generate(char*** board, int rows, int cols, int layers,
} while(layer[x][y][layer] != ' ');
...
}
+'endemplace
-Без абстракция отново ще пренапишем голяма част от [`generate`] функцията, за да се побере на слайда съм оставил само "най-сложната" промяна - случайните стойности.
-Пак, имаше решения на подточката, без преизползване на [`generate`] за един слой.
-Айде да разгледаме решението, заедно с абстрахираната [`generate`].
+Без абстракция отново ще пренапишем голяма част от {` generate} функцията, за да се побере на слайда съм оставил само "най-сложната" промяна - случайните стойности.
+Пак, имаше решения на подточката, без преизползване на {` generate} за един слой.
+Айде да разгледаме решението, заедно с абстрахираната {` generate}.
:slide
-Добро - [`do(X)`]
+Добро - {` do(X)}
```cpp
void generateLayer(char** layer, int rows, int cols,
@@ -357,14 +364,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 +379,7 @@ void generate(char*** board, int rows, int cols, int layers,
.unordered
- Целта на коментарите е да направи разбиране и модификация на код по-лесни.
-- В повечето случаи, коментарът трябва да ни дава [*нова*] информация или да [*свърже*] парчета информация
+- В повечето случаи, коментарът трябва да ни дава {i нова} информация или да {i свърже} парчета информация
Да разгледаме следния пример.
@@ -401,7 +408,7 @@ for (int i = 0; i < rows; i++) {
}
-При добрия вариант, получаваме нова информация, че това запълване се случва понеже ще подаваме към някаква си [`functionX`], и че евентуално може да има други функции, при които празни клетки може да не се обозначават с [`'\0'`].
+При добрия вариант, получаваме нова информация, че това запълване се случва понеже ще подаваме към някаква си {` functionX}, и че евентуално може да има други функции, при които празни клетки може да не се обозначават с {` '\0'}.
В лошия можем да стигнем до същото заключение като погледнем самия код.
:comment
@@ -412,9 +419,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!
-&mdash; Edsger W. Dijkstra, [url https://www.cs.utexas.edu/users/EWD/ewd02xx/EWD249.PDF Notes on Structured Programming, On our inability to do much]
+&mdash; 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 +431,13 @@ for (int i = 0; i < rows; i++) {
-Умението да разрешиш един проблем [*не те*] прави добър програмист, да го разрешиш по четим, разширяем, лесен, ефикасен, ... начин [*те*] прави добър програмист.
+Умението да разрешиш един проблем {i не те} прави добър програмист, да го разрешиш по четим, разширяем, лесен, ефикасен, ... начин {i те} прави добър програмист.
:slide35
## Изводи
-[$br4]
+{br4}
.unordered
- Форматирайте/подравнявайте си кода, искате лесно четене