Сигурно вече сте видели, че задача 6 е проверена и на част от решенията има коментари :)
Тъй като не всяко решение има подробен коментар, тук ще направя един обзор на често срещани проблеми в решенията.
Нестилови бележки
Основният (и май единствен) често срещан не-стилов проблем е при избора на място за методите, генериращи ASCII
и HTML
код.
Главната част от задачата се състои точно в това да организирате кода на логически единици (класове и модули), всеки от които има единствена задача.
Например, задачата на Turtle
е да създаде една матрица, която да запълни с числа чрез DSL. Turtle
не трябва да се интересува от това как се рисува после тази матрица.
В голям брой решения има вариации на следния код:
def draw(canvas = nil, &block)
instance_eval &block
case canvas
when Canvas::ASCII then canvas.draw_ascii(@canvas)
when Canvas::HTML then canvas.draw_html(@canvas)
else @canvas
end
end
В някои решения методите draw_ascii
и draw_html
са в класа Turtle
. За това са отнемани точки, защото така класът започва да се грижи за твърде много неща и в даден момент става неуправляем. Има прекалено много различни видове логика на едно място - методи, които без проблем могат да са самостоятелни. Ако това беше реален код в програма и се наложи да се добави още един вид рендериране, този код ще расте и ще се оплита още повече.
Да се върнем на горния пример. Методите са в съответните им класове, но все още има проблем - класът Turtle
трябва да знае за всеки един вид canvas. Хубаво е да се стараем да правим нещата така, че добавяне на (сравнително очаквана) функционалност да става с минимален брой промени по минимален брой файлове. Ако се наложи да добавим нов вид Canvas
, трябва да напишем нов клас, но и да отидем в Turtle#draw
и да го добавим и там. Последното не винаги е очевидно, че трябва да се случи. В един по-голям проект много лесно може да се пропусне. Особено ако извикването му става на повече места.
def draw(canvas = nil, &block)
instance_eval &block
if canvas
canvas.draw(@canvas)
else
@canvas
end
end
Това решава проблема. Не се интересуваме какво ни е подадено за canvas, стига то да има метод draw
, приемащ матрица.
Хубаво е да се стремим да свеждаме логическите разклонения до минимум. Логически разклонения са основно if
-овете и други конструкции, които правят различни неща в зависимост от някакво условие. Тестването (автоматизирано и ръчно) на код с малък брой логически разклонения е много по-лесно.
Горният if
може да се премахне като просто си дефинираме един canvas Matrix
:
module Canvas
class Matrix
def draw(canvas)
canvas
end
end
end
class Turtle
...
def draw(canvas = Canvas::Matrix.new, &block)
instance_eval &block
canvas.draw(@canvas)
end
end
Така draw
винаги прави едно и също. В автоматизирани тестове ще имаме два случая - ако е подаден canvas
, и ако не е. Във втория случай очакваното поведение ще е същото - да се викне метода draw
на подаденото.
Стилови бележки
- Не дефинирайте класове по този начин:
class TurtleGraphics::Turtle
. Така в тялото на класа търсенето на константи (и други класове) се променя. Не се търси вTurtleGraphics
. Ако напишетеCanvas::ASCII
, очаквайки да ви върнеTurtleGraphics::Canvas::ASCII
- няма да се получи. - Не използвайте класови променливи вместо константи. Избягвайте ги и за други неща. Ако нещо трябва да е "статично" и не се променя - трябва да е константа.
- Паралелното присвояване е прекалено излишно използвано. Лоша практика е да правите
@variable_one, @variable_two = variable_one, variable_two
само за да си спестите един ред. Кодът става по-неясен, защото на един ред се случват повече неща. А като трябва да се добави нова променлива или стават три неща, или се губи консистентност и третият става отделен. - Използвайте
private
като маркер, не като модификатор към всеки метод.private
се слага сам на ред, огражда се с празни редове и под него се пишат методите. Забравете заprivate def име() ...
. Не пишем на Java/C#/PHP/... :) - Не следвайте сляпо условието. В
ASCII
сме дефинирали символите като интервали от стойности, но това не означава, че най-добрият начин да се напише в код е да се изгенерират интервалите. Сметката е много проста:(intensity * (@symbols.size - 1)).ceil
- Не конкатенирайте низове. Използвайте или интерполация, или форматиране, или
Array#join
.reduce
също е неподходящ начин за това, защото вкарва излишна сложност, особено ако тялото му не е просто. - Избягвайте създаването на "builder" методи, като
def closing_tags "</body></html>" end
. HTML-а е идеален случай за използване на форматиране. Целият HTML е един темплейт, в който просто трябва да се вкарат някакви неща (било то чрез интерполация или форматиране). От "builder" методи има смисъл, ако са сравнително независими, могат да се параметризират и има много варианти за структурата на крайния резултат. В случая, като се промени нещо от едната част на HTML-а е много вероятно да се промени и другата част. - Не оставяйте празни редове след дефиницията на клас или модул. Пише го в style guide-а.
-
"<td>#{format('%.2f', intensity)}</td>"
и'<td>%s</td>' % format('%.2f', intensity)
са катоif a == true then true else false end
. Защо не просто'<td>%.2f</td>' % intensity
? Между другото,format(string, data)
иstring % data
правят едно и също. - В доста решения има
if
-ове, които много лесно могат да се заменят с оператора%
.@x % @width
вместоif @x >= @width then 0 elsif @x < 0 then @width - 1 else @x end
- Не слагайте коментари на затварящия
end
на клас или модул -end # HTML
. Ако сте си свършили работата добре, класовете ще са достатъчно малки и ще си личи по индентацията кое от кой клас е. - Не дефинирайте помощни модули/класове извън вашия. Така рискувате да предефинирате функционалност на вече създаден клас с това име. Така ще се счупи и вашият, и чуждият код едновременно.
TL;DR;
Ако не искате да ви вземем точка от следващото домашно - прочетете бележките :)
Коментари и въпроси
Ако не сте съгласни с някое от тези неща - напишете си мнението, подкрепено с доводи "защо". Нека стане дискусия - така се ражда истината. Питайте и ако нещо ви интересува защо е направено така в нашето решение.
Също, ще се радвам ако някой, който си е писал тестове за задачата и иска да получи коментари за тях, ги постне тук :)