За булевите параметри

9 views
Skip to first unread message

Petyo Ivanov

unread,
Nov 27, 2009, 3:13:39 AM11/27/09
to Software craftsmanship Bulgaria
Здравейте,

От известно време установих, че не пърхам от радост като видя функция
с булеви параметри. Извикването изглежда особено неясно:

<code>
do_something("string", true, true, false)
</code>

Да оставим че параметрите са повече от желателното. Какво правят
горните флагове? Трябва да се гледа документацията, трудно се помни, и
тн.

Решение номер 1: константи или енъми. Не ми харесва, защото пак не се
помнят лесно, пък и утежняват положението.

Решение 2: (руските колеги го правят, говорим за руби). дефаултва
параметъра до false, и подава символ. Донякъде е добре, само че остава
именоването на символа на въображението на потребителя.

<code>
def eat(fruit, with_leaves = false) ... end
eat(apple, :with_leaves)
</code>

Решение 3: Хеш. В релсите като е ли са така. Обаче пък е екстремно,
особено ако е един параметър. Пък и разваля сигнатурата на метода, пак
имаш нужда от документацията. А и някакси товари имплементацията.

<code>
def eat(fruit, options = {})
options[:with_leaves] ||= false
... end
eat(apple, :with_leaves => false)
</code>

Решение 4: Named parameters. Чич' Джим (автора на рейката) говори за
такъв тип рефакторинг, където чупим зависимостта на поредността на
параметри, за сметка на имената. В ruby 1.9 вече ще дойдат (ако влезе
в масова употреба преди Софи Маринова да се ожени за сина си за
пореден път). Което май най така добре ми действа на слуха и на
зрението.

Проблем ли е това според вас?

Серж Ованесян

unread,
Nov 27, 2009, 3:17:30 AM11/27/09
to software-crafst...@googlegroups.com
I like enums. 

Но и работя в интегрирана среда(VS) където са доста удобни и не се налага да ги помниш.

Инак варианта с символа звучи интересен.

2009/11/27 Petyo Ivanov <unde...@gmail.com>

Nikolay Bachiyski

unread,
Nov 27, 2009, 3:31:26 AM11/27/09
to software-crafst...@googlegroups.com
2009/11/27 Petyo Ivanov <unde...@gmail.com>:

> Здравейте,
>
> От известно време установих, че не пърхам от радост като видя функция
> с булеви параметри. Извикването изглежда особено неясно:
>
> <code>
> do_something("string", true, true, false)
> </code>
>

Скоро и аз се спънах в това достатъчно сериозно, че да се замисля. И
аз не обичам ttftftftft поредиците. Иде ми да ги заменя с побитови
маски.

>...


>
> Решение 2: (руските колеги го правят, говорим за руби). дефаултва
> параметъра до false, и подава символ. Донякъде е добре, само че остава
> именоването на символа на въображението на потребителя.
>
> <code>
> def eat(fruit, with_leaves = false) ... end
> eat(apple, :with_leaves)
> </code>
>

И аз съм в този отбор. Отчасти защото в PHP другите методи са още по-грозни.

Моята аргументация:
http://codex.wordpress.org/WordPress_Coding_Standards (най-отдолу)

Два реда документация решават проблема с въображението. Случва се
понякога някой да сбърка една буква и да се чуди известно време какво
се случва. Ако не ме мързи проверявам дали аргумента в списък
(частичен, гъвкав и несемантичен enum).

> Решение 3: Хеш. В релсите като е ли са така. Обаче пък е екстремно,
> особено ако е един параметър. Пък и разваля сигнатурата на метода, пак
> имаш нужда от документацията. А и някакси товари имплементацията.
>

Изричен хеш само ако имаш много аргументи.

> Решение 4: Named parameters.
> ...


> Което май най така добре ми действа на слуха и на зрението.
>

О, да. Не знам дали си цъкал python, ама там да ти е кеф да си
параметризираш функциите.

По тази точка аз тъжен:
http://www.php.net/~derick/meeting-notes.html#named-parameters

Happy hacking,
Н.

Stefan Kanev

unread,
Nov 27, 2009, 3:32:13 AM11/27/09
to software-crafst...@googlegroups.com
Това много зависи от конкретната функция. Твърдо против булеви параметри които вкарват логическа кохезия съм. Колкото до решенията ти.

1. Нито константи, нито енъми не ми харесват. So C.
2. Това със символа е хитро. Не знам как си влияе на рефакторинка, обаче. Не знам и какво става ако имаш повече от един и искаш да подадеш само втория
3. Хеш ми харесва най-много.
4. В Ruby 1.9 няма named parameters. Объркал си се. Това е специален синтаксис за хеша.

На базата на параметрите, аз бих си помислил да вкарам това в малко обектче с attr accessor-и, за да можеш да правиш така:

collector = DataCollector.new :include_blanks => true
collector.escape_html = false
result = collector.execute

Повечко код е, но е доста по-прозрачно и има повече fail-safe. Допълнително ти имаш и обект, което си е плюс. Разбира се, предпочитам да ги държа до минимум.

2009/11/27 Серж Ованесян <sovan...@gmail.com>

Petyo Ivanov

unread,
Nov 27, 2009, 4:04:23 AM11/27/09
to software-crafst...@googlegroups.com
4. Да, няма... Че какво съм гледал? Старост нерадост.

2009/11/27 Stefan Kanev <stefan...@gmail.com>:


> Това много зависи от конкретната функция. Твърдо против булеви параметри
> които вкарват логическа кохезия съм. Колкото до решенията ти.
> 1. Нито константи, нито енъми не ми харесват. So C.
> 2. Това със символа е хитро. Не знам как си влияе на рефакторинка, обаче. Не
> знам и какво става ако имаш повече от един и искаш да подадеш само втория
> 3. Хеш ми харесва най-много.

Ем и на мен ми харесва, ама пак са недоклатени named parameters
(именовани или именувани параметри?). Между другото, и JavaScript-a
страда от същия проблем. Гледах скоро кода на jqGrid, две страници код
за сетване на параметрите.

> 4. В Ruby 1.9 няма named parameters. Объркал си се. Това е специален
> синтаксис за хеша.
>
> На базата на параметрите, аз бих си помислил да вкарам това в малко обектче
> с attr accessor-и, за да можеш да правиш така:
> collector = DataCollector.new :include_blanks => true
> collector.escape_html = false
> result = collector.execute

> Повечко код е, но е доста по-прозрачно и има повече fail-safe. Допълнително
> ти имаш и обект, което си е плюс. Разбира се, предпочитам да ги държа до
> минимум.

Абе да имаш обект чак да е плюс винаги...

Lupi

unread,
Nov 27, 2009, 4:07:18 AM11/27/09
to Software craftsmanship Bulgaria
От скоро (2-3 месеца ако бъдем точни) пиша на Objective C за iPhone на
Mac. Езикът доста ме кефи, нещо средно между С и С++, обаче без
огромна част от ненужните неща в С++. Един типичен function call там
изгбежда така:

[self.navigationController pushViewController:registerViewController
animated:YES];

Функцията pushViewController пък се дефинира така

- (void) pushViewController:(UIViewController *)controller animated:
(BOOL)isAnimated
{
}

Тва примерно, по памет. Както виждаме параметрите имат тип, име и
_label_. Това въпросното animated: е всъщност label-a. А пък името на
функцията е label-a на първия параметър. Страшно удобно и за функции с
много параметри става адски удобно като се пише

[object functionName:param
withTitle:@"Some title"
andX:10
andY:10
andZ:20];

Също няма true и false, а има YES и NO.

С две думи, кой как се уредил.

А за enum-ите - може да са C-подобни, но поне да не са all-caps. Като
видя нещо от рода на

TT_ROW_HEIGHT
TT_TOOLBAR_HEIGHT

и ме фаща разстройство :))))))

On 27 Ноем, 10:31, Nikolay Bachiyski <n...@nikolay.bg> wrote:
> 2009/11/27 Petyo Ivanov <under...@gmail.com>:

Stefan Kanev

unread,
Nov 27, 2009, 4:14:24 AM11/27/09
to software-crafst...@googlegroups.com
Абе да имаш обект чак да е плюс винаги...

Имаш следните неща: 
  • По-гъвкав е за refactoring (extract method, например)
  • Повече място да добавяш логика/функционалност.
  • Може да пази състояние, което.
  • Ако имаш по-голям fan-in можеш да си конфертираш параметрите в setter-ите, не в един голям code blob в началото на метода. 
  • Ако имаш набор от параметри, който ползваш на няколко места, можеш да предаваш един такъв обект.
  • Можеш да конструираш извикването на части. Иначе трябва да събереш всички параметри в променливи
Разбира се, обектите са по-скъпи (като learning curve). Но ако срещна сигнатура на метод, която не ми харесва, ще си задам въпроса "Ако вкарам това и няколко други неща зад обект, той ще има ли смисъл?". Така че си е trade-off. Правиш го просто и "леко мръсно" или чисто и "леко тежко". Казвам "леко", понеже и двата варианта в Ruby са евтини за промяна към другия.

Но не, не винаги е плюс. Нищо не е плюс винаги, ако сме извън контекст :)

Stefan Kanev

unread,
Nov 27, 2009, 4:25:58 AM11/27/09
to software-crafst...@googlegroups.com
Само да добавя, че всъщност това не са наименовани параметри, а си е част от името на метода. Просто синтаксиса за извикване е шантав. Ако имаш
[person setWeight:80 andHeight:180] това е един метод, чието име е setWeight:setHeight: и према два аргумента. Мнението ми за това е смесено, но два недостатъка ми се набиват на очи.

Първо, аргументите по подразбиране не се получават. Има нужда да overload-ваш стабилно (setWeight:, setWeight:setHeight:). Никак не харесвам да нямам аргументи по подразбиране. Често нещата стават много по-елегантно с тях.

Второ, post-curcumfix нотацията. Виждам голям зор да пиша извиквания в тоя стил, особено когато искам да ги chain-на. Или още по-лошо, да предам резултатите на един метод в друг. След като съм написал първия, трябва да се върна в началото на реда, да отворя скоба да напиша сигнатура и след това да отида на края на реда да затворя скобата.

Разбира се, Cocoa-та е направен приятно и там е хубаво. Но там най-големия му плюс е че замества JavaBean-like property-тата.

2009/11/27 Lupi <liamh...@gmail.com>

Hristo Deshev

unread,
Nov 27, 2009, 4:31:29 AM11/27/09
to software-crafst...@googlegroups.com
Хора, аз булевите параметри ги възприемам като симптом. И предпочитам да лекувам основния проблем вместо да мисля еквилибристики със символи, енъми, хешове и други такива техники за замитане на боклука под килима.

Откъде идвам: Clean Code книгата на чичо Боб (силно препоръчвам на всеки в групата особено имайки предвид, че подзаглавието й е A Handbook of Agile Software Craftsmanship). Там авторът ми отвори очите, че булевият параметър разкрива нарушение на т.нар. Single Responsibility Principle (SRP). Методът ти реално върши две неща и използва параметъра да познае кое от двете да направи. Казано по друг начин, наблъскали сме два метода в един.

Решението е да отделим два метода. Карам от примера на Петьо:

def eat_fruit_with_leaves(fruit)
...
end

def eat_fruit_without_leaves(fruit)
...
end

Ако ви трябва метод само eat метод, където не ви пука дали плода е с листа, можете да предадете ламбда като параметър (или да вземете ламбдата от някой друг метод), като тази ламбда ще знае кой от двата метода да викне. Обръщам на C#, защото съм скаран с Руби:

void Eat(Fruit victim)
{
    var eatAction = this.EatActionFor(victim);
    eatAction(victim);

Гледайки горното сигурно започва да ви става гадно. Значи извадихме истинския проблем на бял свят. Имплементацията на EatActionFor не трябва да седи извън Fruit класа, защото най-вероятно само проверява типа на плода. Можем да я преместим в класа на плода. То като ще е така, направо да преместим и Eat метода там и да използваме овъррайдване в различните плодове с листа и без листа. Ето ви и хубаво решение без дупликация, без булеви параметри и без код, който бърка в червата на чужди обекти, за да разбере какво да им причини.

Поздрави,
Христо

Stefan Kanev

unread,
Nov 27, 2009, 6:39:36 AM11/27/09
to software-crafst...@googlegroups.com
Това аз го знам като логическа кохезия от Code Complete. Напълно сългасен съм че е зло и твоето е добър refactoring. Обаче не винаги наличието на булев параметър е зло. Ето ти един пример.

def search_for(phrase, load_bodies = false)
  solr.query :q => phrase,
             :qf => (load_bodies ? 'title' : 'title body'),
             :hl => true,
             :fq => 'published_at:[%s TO *]' % 1.month.ago.iso8601
  end
end

Имаш full-text търсачка, искаш да получаваш заглавията на статиите, които търсиш. Понякога искаш да получаваш и съдържанието на статията. solr в горния код е обект който идва от бибилотека (rsolr) и това е начина да го интерфейсваш. Кратките имена идват от, отново, от search engine-а.

Въпроса е -- наистина ли има смисъл да изваждаш това в два отделни метода? Всичко което ми хрумва съдържа повторение или е ненужно сложно за този пример. 

Би могъл направиш search_for частен метод и да публикуваш search_for_titles и search_for_titles_and_bodies (примерно), но така просто местиш проблема зад публичния интерфейс (което пак е нещо). Но пък това рефактор който променя сигнатурата би бил по-труден, понеже трябва да промениш сигнатурата на три метода, не един. Какво мислиш по въпроса, Христо?

2009/11/27 Hristo Deshev <hri...@deshev.com>

Hristo Deshev

unread,
Nov 27, 2009, 7:18:15 AM11/27/09
to software-crafst...@googlegroups.com
Здрасти,

Отговорът ми е по-долу.

2009/11/27 Stefan Kanev <stefan...@gmail.com>

Това аз го знам като логическа кохезия от Code Complete. Напълно сългасен съм че е зло и твоето е добър refactoring. Обаче не винаги наличието на булев параметър е зло. Ето ти един пример.

def search_for(phrase, load_bodies = false)
  solr.query :q => phrase,
             :qf => (load_bodies ? 'title' : 'title body'),
             :hl => true,
             :fq => 'published_at:[%s TO *]' % 1.month.ago.iso8601
  end
end

Имаш full-text търсачка, искаш да получаваш заглавията на статиите, които търсиш. Понякога искаш да получаваш и съдържанието на статията. solr в горния код е обект който идва от бибилотека (rsolr) и това е начина да го интерфейсваш. Кратките имена идват от, отново, от search engine-а.

Въпроса е -- наистина ли има смисъл да изваждаш това в два отделни метода? Всичко което ми хрумва съдържа повторение или е ненужно сложно за този пример. 


За пореден път се убеждавам, че универсални формули няма и трябва да действаме спрямо конкретната ситуация. Това тук си плаче просто за нов Query обект, който да ти построи параметрите, които взима solr.query (Ей какви работи си представих за горкия човечец, който е измислил звучните имена q, qf, hl и fq!). Query обектът си има метод за генериране на списък от пропъртитата (свойствата?), които да донесем от търсачката. Ето как си го представям:

def search_for(query)
  query.execute solr
  # или solr.query (do some Ruby magic to splat the hash and pass it to that method)
  end
end

Ако (то сигурно е така) имаме много викания на search_for със фраза в обикновен низ, веднага си представям следното:

search_for (Query::from_s "На баба ти хвърчилото")

а защо не:

search_for "На баба ти хвърчилото".to_query

За бонус точки:
search_for "На баба ти хвърчилото".to_query.include_body

Ей това последното се чете почти като английски! Потупвам се по рамото и приключвам.

Христо

Stefan Kanev

unread,
Nov 27, 2009, 7:56:55 AM11/27/09
to software-crafst...@googlegroups.com
А, не ти позволявам да monkey patch-ваш String-а по тоя начин. Не му е работа да знае за query.

Също, бих го завъртял така Query.new("Grandma's kite").execute . Дори при мен е точко така. Но не е там въпроса

Въпроса е, че при мен има повече от два атрибута (keywords и load_bodies). Докато си само с тези двата, си мисля че е по-чисто да са аргументи. Това минава в Ruby относително добре. Като се замисля, дори да го пишех на Java, пак щях да го направя по тоя начин.

Но да, и моята мисъл беше, че няма универсална формула, само принципи които трябва да се прилагат в контекст. Затова и мрънкам да давате конкретни примери като говорите за такива неща :)

Hristo Deshev

unread,
Nov 27, 2009, 8:51:47 AM11/27/09
to software-crafst...@googlegroups.com

2009/11/27 Stefan Kanev <stefan...@gmail.com>

А, не ти позволявам да monkey patch-ваш String-а по тоя начин. Не му е работа да знае за query.


Е не съм седнал да подменям length метода, че да ти е толкова гадно. Представи си, че съм малък YAML библиотечък, който ти е сложил метод to_yaml (както е направил един такъв в мойто руби). А и ако не искаш, опцията със статичния метод на Query си остава. Аз съм свикнал на екстендване покрай езиците където това е измислено така, че да не можеш да настъпиш другарчето по пръстите (най-вече C#).

 
Въпроса е, че при мен има повече от два атрибута (keywords и load_bodies). Докато си само с тези двата, си мисля че е по-чисто да са аргументи. Това минава в Ruby относително добре. Като се замисля, дори да го пишех на Java, пак щях да го направя по тоя начин.


Въпрос на вкус. Може би и аз бих почнал с 1-2 параметъра и бих го обърнал на пълен Query обект, като дойде време за трети. Или като ми дойде някой булев параметър. Сега като се замисля много мразя такива и ги избягвам. Сега ударих едно търсене и в текущия ми проект се оказа, че нямам такива методи.

 

Но да, и моята мисъл беше, че няма универсална формула, само принципи които трябва да се прилагат в контекст. Затова и мрънкам да давате конкретни примери като говорите за такива неща :)


Съгласен!

Христо

ivko3

unread,
Nov 27, 2009, 9:12:02 AM11/27/09
to Software craftsmanship Bulgaria
Здравейте,

Не изчетох цялата тази поредица, защото не съм запознат с вашите
любими езици за програмиране. От гледната точка на Java, а и на това,
което съм чел за interface design-а, бих казал, че е доста по-добре да
се ползват два метода с ясни имена, отколкото метод с допълнителен
boolean параметър.

Enum-ите, поне в Java, са също добро решение, особено в случаите,
когато допълнителният метод само усложнява нещата.

Поздрави,
Иван

Stefan Kanev

unread,
Nov 27, 2009, 9:54:30 AM11/27/09
to software-crafst...@googlegroups.com
Е не съм седнал да подменям length метода, че да ти е толкова гадно. Представи си, че съм малък YAML библиотечък, който ти е сложил метод to_yaml (както е направил един такъв в мойто руби). А и ако не искаш, опцията със статичния метод на Query си остава. Аз съм свикнал на екстендване покрай езиците където това е измислено така, че да не можеш да настъпиш другарчето по пръстите (най-вече C#).

В Ruby света имаше няколко интересни казуса, в които почти безвредни monkey-patch-ове създадоха редица главоболия. Като цяло, не съм фен на добавяне на методи във вградени класове, освен ако не правиш много тлъст framework (като Rails).

Не знам и какво да мисля за идиома, в който (привидно) добавяш метод към съществуващ клас. В Objective-C има такава опция, не съм имал възможност да я проуча в реални условия. Интересно ми е дали срещаш някакви проблеми с това, като го правиш в C#. Първия ми инстинкт като видя someObject.foo() е "Хм, класът на someObject имал метод foo()". Туко виж бих си помислил, че е част от SomeObjectClass, пък той бил локално разширение. Може би IDE-то ги оцветява или нещо.

Както и да е, имал ли си проблеми с това?

Hristo Deshev

unread,
Nov 27, 2009, 10:32:28 AM11/27/09
to software-crafst...@googlegroups.com
2009/11/27 Stefan Kanev <stefan...@gmail.com>

Не знам и какво да мисля за идиома, в който (привидно) добавяш метод към съществуващ клас. В Objective-C има такава опция, не съм имал възможност да я проуча в реални условия. Интересно ми е дали срещаш някакви проблеми с това, като го правиш в C#. Първия ми инстинкт като видя someObject.foo() е "Хм, класът на someObject имал метод foo()". Туко виж бих си помислил, че е част от SomeObjectClass, пък той бил локално разширение. Може би IDE-то ги оцветява или нещо.


Не, IDE-то не ги оцветява, но като ховърнеш с мишока вади тултип, от който можеш да разбереш дали е добавен метод. Или това го прави Resharper... не съм сигурен. Иначе не бих си позволил и аз да екстендвам като обезумял. Обикновено се ограничавам до конструктор/каст методи, които имат имена като ToSomething() или AsSomething(). Не съм имал проблеми, защото по имената се познава лесно.

Христо

Stefan Kanev

unread,
Nov 27, 2009, 10:46:36 AM11/27/09
to software-crafst...@googlegroups.com
БТВ, имплементация на подобен идиом за Ruby:


Не смея да я ползвам, обаче.

2009/11/27 Hristo Deshev <hri...@deshev.com>
Reply all
Reply to author
Forward
0 new messages