Решение на Седма задача - ретроспекция от Исмаил Алиджиков

Обратно към всички решения

Към профила на Исмаил Алиджиков

Резултати

  • 6 точки от тестове
  • 0 бонус точки
  • 6 точки общо
  • 1 успешни тест(а)
  • 0 неуспешни тест(а)

Код

REPOSITORY = 'https://github.com/ialidzhikov/ruby-retrospective-2016'
# Двадесет неща, които научих.
#
# 1. На първа задача първоначалният ми подход беше следният - за всяка мерна единица бях създал клас,
# който се грижи за конвентирането от тази единица до всички останали и спрямо дадената единица извиквах
# съответния метод на класа. По този начин трябваше да поддържам преобразования от всяка към всяка мерна
# единица. Благодарение на коментарите на Георги осъзнах, че много по-елегантно и изчистено ще бъде, ако
# си избера дадена мерна единица за междинна и всеки път преобразувам от изходната в междинната и от
# междинната в крайната. По този начин се налага да поддържам преобразования от всички мерни единици в
# една конкретна и от тази конкретна до всички останали. Доста по-четимо и елегантно.
# 2. В условието на първа задача изрично е казано, че няма да се подават мерни единици различни от 'C', 'F'
# и 'К', а аз имам добавена логика, която проверява дали подадената единаца е сред трите и в случай, че
# не е, raise-вам ArgumentError. Сега като погледна мисля, че тези проверки са ненужни в контекста на
# задача и ги премахвам. Последното правило на Кент Бек за прост и ясен дизайн - "and finally it must be
# the smallest code that meets all of the above.".
# 3. В първа задача имам Hash, който пази за всяко вещество инстанция, която има полета за melting_point и
# boiling_point. Ключовете в Hash-a са string-ове и ги сменям на символи. Причините са, че символите са
# immutable и не се тревожим за това дали сме променили някой ключ; символите заемам по-малко памет,
# понеже се интернират; и от не толкова съществено значение, хеширането на string е със сложност O(n), а
# на символ е с константна сложност.
# 4. На втора задача имах неминаващ тест, заради това че бях разбрал условието погрешно. Бях писал и до вас
# така че не е голяма болка. По условие беше написано, че #reshape преобразува текущия хеш в нов с
# различна структура и аз го бях изтълкувал, че трябва да променя self и ми гърмеше тест, който
# проверява дали self-a се променя. Оправих си това малко бъгче.
# 5. В контекста на втора задача разбрах, че #clone и #dup правят плитки (shallow) копия и ако искаш
# дълбоко копие на обект, трябва сам да си го напишеш.
# 6. Във втора задача относно метода #reshape на Hash вместо да копирам подадения shape в нов и да му
# извиквам #fetch_deep, много по-елегантно е да извикам #map, като по този начин избягвам и помощна
# функция за дълбоко копиране.
# 7. На втора задача забелязвам, че мога да избегна проверката за Array в #fetch_deep метода на Hash. Мога
# да дефинирам #fetch_deep на Array и по този начин да се уверя, че и двете структури ще handle-нат
# метода - duck typing.
# 8. На трета задача прибягнах до създаване на класове контейнери, които да ми държат съответно аргументите
# и опциите. Класът за аргументите симулира частично опашка, като той може да бъде заменен от масив,
# като при parse-ване просто се отделят опциите от аргументите. Аналогично може да бъде заменен и класът
# контейнер за опциите.
# 9. На трета задача логиката за това дали дадена опция може да прихване даден аргумент се намираше в
# класа контейнер, като от гледна точка на обектно-ориентирания дизайн много по-добре е тази логика да
# стои в самата опция.
# 10. На трета задача OptionWithParameter наследява от Option и не override-ва метода #execute, който
# изпълнява подадения блок с runner аргумента. CommandRunner класът решаваше дали на дадена опция да
# подаде true или съответния параметър, като по-добър подход би бил OptionWithParameter класът да
# override-не #execute и сам да си parse-не параметъра. От друга старана #execute на Option класа няма
# да се интересува от подадения му параметър и ще извика блока с true.
# 11. На трета задача при построяване на help съобщението подходът, който бях предприел беше да си съставя
# отделните части и накрая да използвам интерполация. В случая с #help се получава доста по-изчистено,
# когато се конкатенира към дадена променлива.
# 12. В четвърта задача добавям тестови case-ове за сравненията, понеже има брутални решения, където даден
# оператор е хард-коднат да връща само определена стойност. Определено трябваше да включа повече expect
# клаузи във въпросните тестове.
# 13. В четвърта задача използвах грешно конструкцията let, като бях изнесъл повтарящи се обекти. Това
# влоши четимостта на тестовете. За да видиш какви обекти използва тестър, трябваше да scroll-неш до
# началото на файла, където се инициализираха. Основното свойство на unit тестовете е да бъдат възможно
# най-опростени, за да може някой като погледне веднага да разбере за какво става въпрос.
# 14. В четвърта задача имах логически дупки. Например тествах дали може да се създаде версия с zerolike
# string-ове (Version.new, Version.new('') или Version.new('0')). Проблемът се състоеше в това, че в
# няколко теста по-надолу ползвах абсолютно същите zerolike аргументи, за да тествам различна
# функционалност. Тестът за това дали може да се създаде версия с zerolike string-ове бяха безсмислени,
# защото ако те фейлнеха, то със сигурност някой от следващите щеше да фейле.
# 15. Имах представата, че 1 тест е 1 или няй-много 2, 3 expect-a/assert-a. С четвърта задача разчупих тази
# своя представа, понеже голяма част от тестовете, които бях писал преди задачката съдържаха най-често
# 1, 2 expect-a.
# 16. В контекста на четвърта задача разбрах, че let може да се използва и в describe блок.
# 17. В пета задача бях използвал class_eval { attr_accessor attribute }, като това е безсмислено - сетвам
# self-a на блока на self. attr_accessor attribute е достатъчно.
# 18. В пета задача имплементирал съм генерирането на ново id по такъв начин, че всеки път броя до
# следващото свободно ID. Просто increment върши чудесна работа, но в примерните тестове се създават
# модели с id-та и за да няма 2 модела с едно и също id, взимам следващото свободно. Но явно този check
# тестовете не го покриват.
# 19. В пета задача #data_store методът се използва с 2 цели - за сетване на ново хранилище и за достъп до
# вече зададеното. Моят подход към метода беше да примера произволен брой аргументи (*args) и да
# проверя дали масивът не е празен и спрямо това да върна data_store или да сетна такъв. Една идея
# по-елегантен подход е методът да приема параметър със стойност по подразбиране (store = nil).
# 20. The wrong abstraction - беше доста полезно да разбера за това понятие и мисля, че module Store не е
# пример за такова.

Лог от изпълнението

From https://github.com/fmi/ruby-retrospective-2016
 * branch            master     -> FETCH_HEAD
HEAD is now at a22cf37 Set rubocop version to 0.46.0 to fix obsolete cop errors
Cloning into 'submission'...
HEAD is now at 6ac6e9c Refactor task 05 solution
From /tmp/ruby-retrospective-2016/checker
 * branch            master     -> FETCH_HEAD
 * [new branch]      master     -> upstream/master

Changes URL:
https://github.com/ialidzhikov/ruby-retrospective-2016/compare/1f710b00c26...6ac6e9cc77d

'tasks/1/solution.rb' -> '/tmp/ruby-retrospective-2016/checker/tasks/1/solution.rb'
'tasks/2/solution.rb' -> '/tmp/ruby-retrospective-2016/checker/tasks/2/solution.rb'
'tasks/3/solution.rb' -> '/tmp/ruby-retrospective-2016/checker/tasks/3/solution.rb'
'tasks/4/solution.rb' -> '/tmp/ruby-retrospective-2016/checker/tasks/4/solution.rb'
'tasks/5/solution.rb' -> '/tmp/ruby-retrospective-2016/checker/tasks/5/solution.rb'
Inspecting 1 file
.

1 file inspected, no offenses detected
.................

Finished in 0.00464 seconds
17 examples, 0 failures
Inspecting 1 file
.

1 file inspected, no offenses detected
...............

Finished in 0.00531 seconds
15 examples, 0 failures
Inspecting 1 file
.

1 file inspected, no offenses detected

...............

Finished in 0.00923 seconds
15 examples, 0 failures
Inspecting 1 file
.

1 file inspected, no offenses detected
....................

Finished in 20.16 seconds
20 examples, 0 failures
Inspecting 1 file
.

1 file inspected, no offenses detected
.........................

Finished in 0.02052 seconds
25 examples, 0 failures
.

Finished in 0.00168 seconds
1 example, 0 failures

История (1 версия и 0 коментара)

Исмаил обнови решението на 16.01.2017 01:41 (преди над 7 години)

+REPOSITORY = 'https://github.com/ialidzhikov/ruby-retrospective-2016'
+
+# Двадесет неща, които научих.
+#
+# 1. На първа задача първоначалният ми подход беше следният - за всяка мерна единица бях създал клас,
+# който се грижи за конвентирането от тази единица до всички останали и спрямо дадената единица извиквах
+# съответния метод на класа. По този начин трябваше да поддържам преобразования от всяка към всяка мерна
+# единица. Благодарение на коментарите на Георги осъзнах, че много по-елегантно и изчистено ще бъде, ако
+# си избера дадена мерна единица за междинна и всеки път преобразувам от изходната в междинната и от
+# междинната в крайната. По този начин се налага да поддържам преобразования от всички мерни единици в
+# една конкретна и от тази конкретна до всички останали. Доста по-четимо и елегантно.
+# 2. В условието на първа задача изрично е казано, че няма да се подават мерни единици различни от 'C', 'F'
+# и 'К', а аз имам добавена логика, която проверява дали подадената единаца е сред трите и в случай, че
+# не е, raise-вам ArgumentError. Сега като погледна мисля, че тези проверки са ненужни в контекста на
+# задача и ги премахвам. Последното правило на Кент Бек за прост и ясен дизайн - "and finally it must be
+# the smallest code that meets all of the above.".
+# 3. В първа задача имам Hash, който пази за всяко вещество инстанция, която има полета за melting_point и
+# boiling_point. Ключовете в Hash-a са string-ове и ги сменям на символи. Причините са, че символите са
+# immutable и не се тревожим за това дали сме променили някой ключ; символите заемам по-малко памет,
+# понеже се интернират; и от не толкова съществено значение, хеширането на string е със сложност O(n), а
+# на символ е с константна сложност.
+# 4. На втора задача имах неминаващ тест, заради това че бях разбрал условието погрешно. Бях писал и до вас
+# така че не е голяма болка. По условие беше написано, че #reshape преобразува текущия хеш в нов с
+# различна структура и аз го бях изтълкувал, че трябва да променя self и ми гърмеше тест, който
+# проверява дали self-a се променя. Оправих си това малко бъгче.
+# 5. В контекста на втора задача разбрах, че #clone и #dup правят плитки (shallow) копия и ако искаш
+# дълбоко копие на обект, трябва сам да си го напишеш.
+# 6. Във втора задача относно метода #reshape на Hash вместо да копирам подадения shape в нов и да му
+# извиквам #fetch_deep, много по-елегантно е да извикам #map, като по този начин избягвам и помощна
+# функция за дълбоко копиране.
+# 7. На втора задача забелязвам, че мога да избегна проверката за Array в #fetch_deep метода на Hash. Мога
+# да дефинирам #fetch_deep на Array и по този начин да се уверя, че и двете структури ще handle-нат
+# метода - duck typing.
+# 8. На трета задача прибягнах до създаване на класове контейнери, които да ми държат съответно аргументите
+# и опциите. Класът за аргументите симулира частично опашка, като той може да бъде заменен от масив,
+# като при parse-ване просто се отделят опциите от аргументите. Аналогично може да бъде заменен и класът
+# контейнер за опциите.
+# 9. На трета задача логиката за това дали дадена опция може да прихване даден аргумент се намираше в
+# класа контейнер, като от гледна точка на обектно-ориентирания дизайн много по-добре е тази логика да
+# стои в самата опция.
+# 10. На трета задача OptionWithParameter наследява от Option и не override-ва метода #execute, който
+# изпълнява подадения блок с runner аргумента. CommandRunner класът решаваше дали на дадена опция да
+# подаде true или съответния параметър, като по-добър подход би бил OptionWithParameter класът да
+# override-не #execute и сам да си parse-не параметъра. От друга старана #execute на Option класа няма
+# да се интересува от подадения му параметър и ще извика блока с true.
+# 11. На трета задача при построяване на help съобщението подходът, който бях предприел беше да си съставя
+# отделните части и накрая да използвам интерполация. В случая с #help се получава доста по-изчистено,
+# когато се конкатенира към дадена променлива.
+# 12. В четвърта задача добавям тестови case-ове за сравненията, понеже има брутални решения, където даден
+# оператор е хард-коднат да връща само определена стойност. Определено трябваше да включа повече expect
+# клаузи във въпросните тестове.
+# 13. В четвърта задача използвах грешно конструкцията let, като бях изнесъл повтарящи се обекти. Това
+# влоши четимостта на тестовете. За да видиш какви обекти използва тестър, трябваше да scroll-неш до
+# началото на файла, където се инициализираха. Основното свойство на unit тестовете е да бъдат възможно
+# най-опростени, за да може някой като погледне веднага да разбере за какво става въпрос.
+# 14. В четвърта задача имах логически дупки. Например тествах дали може да се създаде версия с zerolike
+# string-ове (Version.new, Version.new('') или Version.new('0')). Проблемът се състоеше в това, че в
+# няколко теста по-надолу ползвах абсолютно същите zerolike аргументи, за да тествам различна
+# функционалност. Тестът за това дали може да се създаде версия с zerolike string-ове бяха безсмислени,
+# защото ако те фейлнеха, то със сигурност някой от следващите щеше да фейле.
+# 15. Имах представата, че 1 тест е 1 или няй-много 2, 3 expect-a/assert-a. С четвърта задача разчупих тази
+# своя представа, понеже голяма част от тестовете, които бях писал преди задачката съдържаха най-често
+# 1, 2 expect-a.
+# 16. В контекста на четвърта задача разбрах, че let може да се използва и в describe блок.
+# 17. В пета задача бях използвал class_eval { attr_accessor attribute }, като това е безсмислено - сетвам
+# self-a на блока на self. attr_accessor attribute е достатъчно.
+# 18. В пета задача имплементирал съм генерирането на ново id по такъв начин, че всеки път броя до
+# следващото свободно ID. Просто increment върши чудесна работа, но в примерните тестове се създават
+# модели с id-та и за да няма 2 модела с едно и също id, взимам следващото свободно. Но явно този check
+# тестовете не го покриват.
+# 19. В пета задача #data_store методът се използва с 2 цели - за сетване на ново хранилище и за достъп до
+# вече зададеното. Моят подход към метода беше да примера произволен брой аргументи (*args) и да
+# проверя дали масивът не е празен и спрямо това да върна data_store или да сетна такъв. Една идея
+# по-елегантен подход е методът да приема параметър със стойност по подразбиране (store = nil).
+# 20. The wrong abstraction - беше доста полезно да разбера за това понятие и мисля, че module Store не е
+# пример за такова.