What to do about weblib methods with lots of arguments

What to do about weblib methods with lots of arguments

by Tim Hunt -
Number of replies: 19
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
If you have following the Navigation 2.0 stuff in the Roadmap you will know that all the output functions in lib/weblib.php are going to be replaced with something better.

At least I hope the new ones will be better. At the moment I am trying to work out the best way to handle functions like choose_from_menu which currently has 13 parameters. I have written a wiki page with one idea: Development:What to do about weblib methods with lots of arguments

Comments, or alternative suggestions welcome.


Another question is, with functions like choose_from_menu, where the current function name does not do a very good job of describing what the function does (I think), should we change it now (as I did on the wiki page) or should we keep the familiar name?
Average of ratings: -
In reply to Tim Hunt

Re: What to do about weblib methods with lots of arguments

by José Cedeño -
Hi Tim,

Awesome job on refactoring the API. It must feel good to get a chance to clean up things. I think that the second method (listed below) is easier to read and understand without having to lookup the function signature.
$menu
= new moodle_select_menu;
$menu->something = somethingvalue;

The code above is very OO, but another way to aproach this could be similar to how javascript passes information as objects:
$menu = new moodle_select_menu( array(
'something' => 'somethingvalue'
));

The 2nd method, is not as OO as the first one, but it could be readable if people indent the array elements.

In reply to José Cedeño

Re: What to do about weblib methods with lots of arguments

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Yes, I am convinced that code like
$menu = new moodle_select_menu;
$menu->options = $options;
$menu->name = 'outcome_'.$n.'['.$userid.']';
$menu->selectedoption = $outcome->grades[$submission->userid]->grade;
$menu->nothinglabel = get_string('nooutcome', 'grades');
$menu->id = 'menuoutcome_'.$n;
echo $OUTPUT->select_menu($menu);
Is the best way to handle the general API. It leads to nicely self-documenting code, and if that is a bit verbose - well, that is a small price to pay for clarity.

The only thing is that when I was skimming through the code to find examples for my wiki page, I noticed that most of the places we use choose_from_menu just use the first three arguments (and override the default value for the 4th). It would be really annoying if even in the simple case, you had to replace one line of code with five everywhere.

I think I have found a good answer to that. We can provide short-cut factory methods to create moodle_select_menus initialised with the standard options. So, if you just want a simple menu, you can do it with one line of code:
echo $OUTPUT->select_menu(moodle_select_menu::make($options, $name, $selected));
Also, instead of having a completely separate function like choose_from_menu_yesno to handle yes/no menus, we can just have a separate factory method moodle_select_menu::make_yes_no.


In reply to Tim Hunt

Re: What to do about weblib methods with lots of arguments

by Dan Poltawski -
Hmm, what are the major advantages of this approach has over an array of named parameters?

It sounds silly, but remembering the name of both the function and the class name feels like more work for little gain and kind of heavyweight?

I agree now is a great time to rename things like choose_from_menu (though does that mean all our print_ functions should be renamed if they are not now printing?)
In reply to Dan Poltawski

Re: What to do about weblib methods with lots of arguments

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
One advantage is that moodle_select_menu can have PHPdoc comments on each field to explain what that field means. Also, your IDE may be able to auto-complete the field names for you. That is the main advantage of an object over an array.

Also, I would rather type
$menu->name = ...;
then
$menu['name'] = ...;


Also, we use objects elsewhere (events, $a on get_string, database rows). (I know, we don't use them for lib/dml, but the kind of data we pass to those methods methods seems natural as an array to me, somehow.)


A class also lets us put factory methods like moodle_select_menu::make_yes_no() on the class itself, which is more cohesive.


I think I will be dropping print everywhere. There will be a new global $OBJECT which is a moodle_core_renderer (or a subclass of that chosen by the theme). So code will look like
echo $OUTPUT->select_menu($menu);


Your point about having two things to remember (class name and method name) is a good one and I had not considered it before. However, I guess we can ensure that the class name is always the method name with moodle_ prefixed. (e.g. class moodle_select_menu, method $OUTPUT->select_menu(...))


Earlier today I committed the code as far as I had got to lib/outputlib.php so you are welcome to take a look and comment on how it is progressing. I really do want to know if people don't like this. It is going to affect everything I do, so it would be really, really, bad if I get it wrong. Please look sooner, rather than later, and give voice to your concerns.

In reply to Tim Hunt

Re: What to do about weblib methods with lots of arguments

by Dan Poltawski -

Also, I would rather type
$menu->name = ...;
then
$menu['name'] = ...;


Agreed, but we could also use an anonymous object, as is the case of $a and database rows (I think!). I prefer the object syntax also.


A class also lets us put factory methods like moodle_select_menu::make_yes_no() on the class itself, which is more cohesive.

Once again agreed, but if I become an external module developer for a day, surely that is an implementation detail and I just want the simplest api possible? $OUTPUT->yes_no() which call that or does other magic?


Your point about having two things to remember (class name and method name) is a good one and I had not considered it before. However, I guess we can ensure that the class name is always the method name with moodle_ prefixed. (e.g. class moodle_select_menu, method $OUTPUT->select_menu(...))


I *really* dont like the prefix moodle, simply because lots of things already use this prefix and it'd be good to distinguish between argument options and other moodle stuff. I'm afraid I don't have any good concise alternatives though.


Earlier today I committed the code as far as I had got to lib/outputlib.php so you are welcome to take a look and comment on how it is progressing. I really do want to know if people don't like this. It is going to affect everything I do, so it would be really, really, bad if I get it wrong. Please look sooner, rather than later, and give voice to your concerns.

Great! I'm trying to keep up to speed and give feedback.

Tim, its really going to be a real shame to see you have to cut back on your core work when you head back to the UK - I wish we could get a better beer supply to .au to help continue this great stuff tongueout
In reply to Dan Poltawski

Re: What to do about weblib methods with lots of arguments

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
The thing we lose by going to an anonymous object is http://phpdocs.moodle.org/HEAD/moodlecore/moodle_select_menu.html, and the defaults for all the fields. I think that is worth having. (We could handle defaults some other way, but it would be trickier.)

The advantage you get by separating the creation of an object that describes a yes/no menu from its output is that between the two you can tinker with it. For example, what if you wanted to output a disabled yes/no menu? We would be in danger of getting back to a $OUTPUT->yes_no() method with 13 arguments, which is where we started.

I am not a big fan of the moodle_ prefix, but I also don't have a better one yet. html_? sad I think we need something, just as some namespacing, so we don't need to obfuscate the main part of the name to avoid clashes. Suggestions welcome.

Thanks for your feedback. More please.
In reply to Tim Hunt

Re: What to do about weblib methods with lots of arguments

by José Cedeño -
One advantage of using objects that has not been mentioned in this thread is extending objects and inheritance. By allowing people to extend the select objects (or some other html elements) people could hook into the various html forms and modify them easily.

What about having a parameter for the $yes_no method? You could do:
html::yes_no($output = false);
So if the output is set to true, it would output html, otherwise it would return a select object that people could manipulate and modify?

What about using html_ for things that would output or deal with html related things, but if something is a form or input element it would use form_ ?
In reply to José Cedeño

Re: What to do about weblib methods with lots of arguments

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Jose, I think you need to read Development:Theme_engines_for_Moodle%3F and/or look at the evolving code in http://cvs.moodle.org/moodle/lib/outputlib.php?view=markup.

If you think MVC, moodle_select_menu is the model. The HTML is actually generated by the moodle_core_renderer class, or a subclass chosen by the theme. This isn't really MVC, becuase the responsibilities of one of my renderer classes is a bit different from the responsibilities of a view, but this is probably the quickest way to explain it).
In reply to José Cedeño

Re: What to do about weblib methods with lots of arguments

by Martín Langhoff -
Let's keep inheritance at a safe distance, except where it has a clear and big payoff.

Inheritance is an "action at a distance" trick, see http://en.wikipedia.org/wiki/Action_at_a_distance_(computer_science) and results in (hidden) tight coupling.

Those two factors were recently found by the FDA to dramatically shorten programmer life expectancy, cause warts in the nether regions and smelly breath.

Really smelly breath smile

Moodle code is quite literal, and that was a major reason in my strong advocacy of Moodle. I evaluated a highly abstracted OOP centric system at the time (Ilias v3) and it did everything by the OOP book.

It was impossible to understand or to debug. I found a couple of security bugs and could not trace where the source of the problem was. And that is the very definition of unworkable code.
In reply to Martín Langhoff

Re: What to do about weblib methods with lots of arguments

by José Cedeño -
@Tim - thanks for the link to the wiki pages, and your comment. It clarified the purpose of the function. I still have a lot to learn about Moodle.

@Martin L - thanks for the info. I had never heard of anti-patterns. That sounds very interesting. I see your point of over using OO techniques and how it might complicate things.
In reply to Tim Hunt

Re: What to do about weblib methods with lots of arguments

by Martín Langhoff -

Personally, I don't care much whether it is array() or object. I am slightly inclined towards arrays because you can write it

foocall(array( file => 'foo',
              dir  => '/bar/',
              otheropt => 99 ));

which is verbose but compact. I seriously dislike very long classnames, parameter names and function calls.

The approach you are proposing makes our code huge width-wise and height-wise. It makes it look exactly like Elgg.

The end result is that you can get a lot less "logic" in one screen, and you need your editor to take the whole screen.

This means that you can't develop with all the other screens you need:

  • 2 or 3 different browsers open
  • tailing apache's error log
  • tailing DB query / error log
  • a shell window for SCM operations, mainly diff'ing

Are there alternatives that keep us with reasonably short code? With less risk of RSI cutting our programming careers? With less dependency on an clunky IDE and a 21 inch monitor?

I am sorry this ended up being a long whining post. But having the code grow while not doing anything new is just fire and motion that increases cognitive load. In most mature projects, a good refactor is one that increases modularity and clarity while reducing line count (and keeping at 80 cols!).

It is of course not the only criteria, but when I read the code of those projects, it is clear and easy to follow, thanks to short but clear names, and brief but strategically placed comments.

In other words:

 Code verbosity != clarity
In reply to Martín Langhoff

Re: What to do about weblib methods with lots of arguments

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
I have a lot of respect for your whining Martín, becuase you have read much more code on many more open source projects than me, and I completely agree code verbosity != clarity.

However, You have not yet persuaded me that I am on the wrong track here.

Undoubtedly my coding style tends towards longer but more descriptive class names than yours, but not, I hope, to the point of being a fault.

I don't believe my changes increase verbosity as much as you fear they will. However, at the moment I am working on converting weblib -> outputlib, not converting code that uses it, so I don't have any convincing diffs to show you.

I do think the cognitive load of parsing a line of code like http://xref.moodle.org/nav.html?mod/assignment/lib.php.source.html#l901 is approaching infinity, and that is only using 8 of the available 13 parameters of choose_from_menu. (Line 916 there is another prime example.)

It is only functions taking many arguments that get this object approach. A simpler example is

print_heading($text, '', 3); // 28 chars.

which becomes

echo $OUTPUT->heading($text, 3); // 32 chars.
In reply to Tim Hunt

Re: What to do about weblib methods with lots of arguments

by Martín Langhoff -

I don't know if you're on the wrong track... did get scared with very verbose lines in your earlier examples smile

// this is good, I wonder if it means h1, so
echo $OUTPUT->heading(str, int);
// could be the shorter, self-explanatory
// and more flexible
echo $OUTPUT->h1(str, int);

In terms of the (great) example in assignment, I wonder about some minor improvements:

  • Can make_grades_menu() morph into a call that wraps around choose_from_menu() and returns the HTML?
  • shorter var names there can help...
  • splitting the call line in the parameters can make it more readable

You probably know all these tricks already. I won't say it's an easy challenge -- it is rather hard.

Will try and think about good examples from other projects. One of the things I do have in mind is that if a given call has too many params, it is perhaps doing too many things, and the approach could be rethought with a bit of a wider scope.

(Like - could the grades-specific "make" call return a richer array or an object, and split the params between make calls and the choose_from_menu() implementation? )

Another question: can we "inline" object creation in a short format? Something like

foocall(obj(foo=>bar, bar=>baz));

if we can do that, and combine it with short method names and param names, then the potential is good smile

In reply to Martín Langhoff

Re: What to do about weblib methods with lots of arguments

by Dan Poltawski -

// this is good, I wonder if it means h1, so

echo $OUTPUT->heading(str, int);

// could be the shorter, self-explanatory

// and more flexible

echo $OUTPUT->h1(str, int);



Well, in standard theme it means in h1, but in the new web 3.0 theme it has rounded corners, some horrendous shimmering effect driven by javascript and the heading is actually floating on a wave which follows the mouse - so it starts to look a lot more than h1. wink
In reply to Martín Langhoff

Re: What to do about weblib methods with lots of arguments

by Nicolas Connault -
I notice that you don't address one of Tim's main points: the ability to properly document the function's parameters. What are your thoughts on that?
In reply to Nicolas Connault

Re: What to do about weblib methods with lots of arguments

by Martín Langhoff -

I am a big fan of named parameters which are self-documenting -- in PHP it's either an object or an array. It is good to be able to prepare it in advance or put it inline. With an array you can do it "inline" with the clumsy array():

 foocall(array( foo => $int,
                bar => $str )); 

This is already a bit verbose vertically and horizontally. It can work really well if you keep funtion/method names short, and varnames short.

The most important thing, in terms of verbosity, is to keep things like varnames and functionnames clear but short, so when you put a comment, it is about something you want the reader to note.

I'll make a liberal adaptation of Tim's example from assignment:

 // Assuming assignment had a "module-specific"
 // "HTML elements creation" object as a helper
 // MHTML for "module HTML"
 echo $MHTML->choose(array(asmt => $this->assignment,
                           grade => $sbm->grade,
                           def => -1
                           focus => false,
                           enabled =  $enabled // true after foo happens
 ));

I've taken lots of liberties here -- it's not accurate. What I want to illustrate is that the humdrum 'boilerplate' of usual settings should be as short and sweet as possible, so that the important comment "this is only trie after foo happens" stands out.

Writing this in markdown probablt messed up indentation smile

Of course, it'd be fantastic if PHP could understand [] or {} to build an "anonymous" array or object.

In reply to Martín Langhoff

Re: What to do about weblib methods with lots of arguments

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Hey! Cheat! Where's the ref! Red card!

You can't just go moving the goal-posts mid-discussion. I'm glad I was awake and noticed. wink

Of coures, you can simplfy the assignment code by postulating the some sort of assignment-specific helper function. You could do that refactoring in Moodle 1.9 just by adding a function to mod/assignment/lib.php that took a few arguments and called choose_from_menu. That would make the assignment code easier to understand.

What we are talking about is what happens inside your hypothetical $MHTML->choose function. I assume that you are planning to do some setup, and then call the generic output select-menu function in outputlib.php. (You surely aren't proposing to duplicate the code for generating the
(Also, PHP arrays need single quotes around the array keys, otherwise you get notices. That adds to the line noise.)
In reply to Tim Hunt

Re: What to do about weblib methods with lots of arguments

by Martín Langhoff -
Sorry! I got down a tangent, I didn't mean this as a reply to your questions.

More to your point. HTML widgets have lots of options, so the problem space is that we have lot of options. Named arrays or objects are the best (still clumsy) way to tackle this.

So lots of named params are in our future, either way. I just hope we can still avoid very long lines with repetitive names.

/me walks defeated (and red-carded!) to the bench.
In reply to Martín Langhoff

Re: What to do about weblib methods with lots of arguments

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Part of me says, with modern IDEs (and even good ol' ctags) who cares. A little yellow box probably pops up telling you what the parameters are.

I just think it's a matter of common sense. These are my random thoughts..

* More classes are a good thing - just to give a namespace if nothing else. I'd quite like to see something like output::select_from_menu(a,b,c) then I know where to find it.
* A small number of parameters in a function does not require things like associative arrays (or objects, same thing different syntax) for parameters surely. Good example - required_param(name,clean_thing)!!
* Once you go beyond some small but arbitrary number of parameters then it becomes confusing, unreadable and error prone. Then is the time to to wrap the function (and it's friends) up in a class and use setter methods for the parameters. Sure, it's more typing but it's clearer by miles.

Just my $00.02 as they say.