i've seen apis, in scripting languages (we use perl , js in our team), use combined getter , setter methods. example, in jquery:
//append element text var element = $('div#foo'); element.text(element.text() + 'abc');
for example, in perl's cgi.pm module:
# read url parameter request $old_value = $cgi->param('foo'); # change value of parameter in request $cgi->param('foo', $new_value);
some generic dao class in our perl codebase uses similar pattern. autogenerated accessors call internal getset()
method, similar this:
sub foo { # read/write accessor foo() property (autogenerated) ($self, $new_value) = @_; return $self->getset('foo', $new_value); } sub getset { ($self, $field, $new_value) = @_; ## snip (omitted magic here) ## # setter mode if (defined $new_value) { ## snip (omitted magic here) ## $self->{data}{$field} = $new_value; ## snip (omitted more magic here) ## } # getter mode return $self->{data}{$field} // ''; }
i see multiple issues design:
setter calls go through getter code path, too, inefficient if have relations, getter has resolve stored id object:
$foo->bar($new_bar); # retrieves bar instance not used
a getter call needs carry
$new_value
argument around. when call perl method (say, 100000 times, usual number of records in reporting , other cronjobs), can cause measureable overhead.setters cannot take undefined value. (a perl specific issue, work around checking argument count instead of argument defined-ness, make autogenerated accessors more complicated, , break manual accessors.)
decreased grepability: if had separate getters , setters (e.g.
foo()
,set_foo()
eachfoo
property), search setter calls using simple grep.even more?
i wonder if there actual benefits combined-setter-and-getter approach, or if it's weird tradition among communities of respective languages/libraries.
synopsis
- having seperate getters/setters or having combined accessors cultural preference. there nothing prevents choosing path less taken.
- most of downsides don't exist. not confuse implementation details problems of stylistic decision.
having different set_
, get_
methods looks self-documenting, but
- are pain write if no autogeneration takes place, and
- are form of static access control: can have
get_
without exposingset_
.
the latter point important fine-grained access control , permission systems used, e.g. in languages java, c#, c++ visibility nuances private/protected/public, etc. these languages have method overloading, writing unified getters/setters isn't impossible, instead cultural preference.
from personal perspective, unified accessors have these benefits
- the api smaller, can make documentation , maintenance easier.
- depending on naming convention, there 3–4 less keystrokes per method call.
- objects feel bit more struct-like.
- i can think of no real downsides.
it personal opinion foo.bar(foo.bar() + 42)
tad easier on eyes foo.setbar(foo.getbar() + 42)
. however, latter example makes clear each method does. unified accessors overload method different semantics. consider feel natural, complicates understanding code snippet.
now let me analyze alleged downsides:
analysis of alleged issues combined getters/setters
setter calls go through getter code paththis isn't neccessarily case, , rather property of implementation looking at. in perl, can reasonably write
sub accessor { $self = shift; if (@_) { # setter mode return $self->{foo} = shift; # if chaining methods, # return $self; } else { # getter mode return $self->{foo} } }
the code paths seperate, except stuff truly common. note accept undef
value in setter mode.
in perl, free inspect context in method called. can have code path executed in void context, i.e. when return value thrown away:
if (not defined wantarray) { be_lazy() }
a getter call needs carry $new_value
argument around […] measureable overhead. again, implementation-specific problem. also, overlooked real performance issue here: accessor dispatch method call. , method calls slow. of course method resolution cached, still implies 1 hash lookup. more expensive variable on argument list.
note accessor have been written like
sub foo { $self = shift; return $self->getset('foo', @_); }
which gets rid of half of i can't pass undef
problem.
… wrong. covered that.
grepabilityi use definition of grepability:
if source file searched name of method/variable/…, site of declaration can found.
this forbids moronic autogeneration like
my @accessors = qw/foo bar/; $field (@accessors) { make_getter("get_$field"); make_setter("set_$field"); }
here, set_foo
wouldn't bring point of declaration (the above snippet). however, autogeneration like
my @accessors = qw/foo bar/; $field (@accessors) { make_accessor($field); }
does fulfill above definition of grepability.
we can use stricter definition if like:
if source file searched declaration syntax of method/variable/…, site of declaration can found. mean “
function foo
” js , “sub foo
” perl.
this requires @ point of autogeneration, basic declaration syntax put comment. e.g.
# autogenerate accessors # sub set_foo # sub get_foo # sub set_bar # sub get_bar @accessors = qw/foo bar/; ...; # above
the grepability not impacted using combined or seperate getters , setters, , touches on autogeneration.
on non-moronic autogeneration
i don't know how autogenerate accessors, result produced doesn't have suck. either use form of preprocessor, feels silly in dynamic languages, or use eval
, feels dangerous in modern languages.
in perl, i'd rather hack way through symbol table during compile time. package namespaces hashes names %foo::
have so-called globs entries, can contain coderefs. can access glob *foo::foo
(note *
sigil). instead of doing
package foo; sub foo { ... }
i also
begin { *{foo::foo} = sub { ... } }
now let's consider 2 details:
- i can turn off
strict 'refs'
, can dynamically compose subroutine name. - that sub … closure!
therefore, can loop on array of field names, , assign them same subroutine, difference each closes on different field name:
begin { @accessors = qw/foo bar/; # sub foo # sub bar $field (@accessors) { no strict 'refs'; *{ __package__ . '::' . $field } = sub { # here old `getset` $self = shift; ## snip (omitted magic here) ## if (@_) { # setter mode $new_value = shift; ## snip (omitted magic here) ## $self->{data}{$field} = $new_value; ## snip (omitted more magic here) ## return $new_value; # or something. } else { # getter mode return $self->{data}{$field}; } }; } }
this greppable, more efficient delegating method , can handle undef
.
the downside reduced maintainability, if maintaining programmer not know pattern.
also, errors originating inside sub reported coming __anon__
:
some error @ script.pl line 12 foo::__anon__(1, 2, 3) called @ foo.pm line 123
if issue (i.e. accessor contains complex code), mitigated using sub::name
, stefan majewsky points out in comment below.
Comments
Post a Comment