In my experience clients and bosses want to minimize expenditure, so coming back is never an option unless they want a new feature or other change. It works well if you're your own boss, or your boss/client actually works on the same code but that is the exception, not the rule.
Full disclosure: I mainly work the german market, but have also worked for people from Washington, Ireland, Finland where the above held true.
]]>First of all, roles are composed into classes. This means that all of a role's methods end up in the class as if they'd been defined there. There's no need to ever call "SUPER::foo". When a class consumes multiple roles, method conflicts can be detected at compile time and the role mechanism can throw an error. This forces developers to manually resolve the conflict, unlike subclassing. I suppose in theory you could write a subclassing mechinism like use parent
that also did this, but why bother?
Second, roles don't have a constructor. This elements the bizarre evil of abstract base classes which can't be inherited, but look semantically like classes which can be constructed.
Of course, this being Perl, there's nothing you can't do with roles that you can't do with multiple inheritance. It's just that roles provide better, safer semantics for sharing code across multiple classes.
Consider the non-inheritance relationship, where one object delegates to another. You can't get the right answer about a type question by asking "Does the delegator inherit from the delegatee?", and you know that if you limit all of your questions about object behavior to "Does this object have a method of this name?" then you run into the false cognate problem (a tree has bark while a dog can bark; a student can bomb a test while a fighter jet can bomb a test target).
What you need is a way of asking "What named collection of behaviors does this entity provide?" without running into the false cognate problem and without dictating the nature of relationship between the entity in question and whatever you think provides that named collection of behaviors.
By consuming a role, you promise that the consumer provides the named behavior defined by that role. Consuming a role often flattens one or more methods into the consumer, but if the consumer already provides those methods, the flattening doesn't have to happen.
Roles give you a way of grouping collections of attributes and behavior under names as well as a default mechanism to provide that collection of behavior in whole or in part to a consumer. That's it.
]]>If a shortcut will do the job for now, that is an acceptable choice only if you actually document why you took the shortcut. Even better if you actually leave behind some comments about what you would have preferred to do or plan to return and finish later.
In my experience (I'm usually the guy who comes in and owns legacy perl code when they want someone to support the app full-time) the technical debt left behind is rarely documented. If anyone was informed of the shortcut at the time it was authored, those individuals have long since forgotten or moved on to another job. So, for the sake of the Perl community and freelancers or full-time Perl hackers alike, please leave some level of "collateral" when racking up "technical debt."
I also believe that this is a key factor as to why many adept IT people (system architects, software developers, et al) have difficulty launching software products for themselves (i.e. too many choices and nobody telling them to "just do it this way for now").
I also feel that this has been validated in my own work experiences as many of the highly profitable companies I've worked for/with have products that are pretty crappy (from a UI/UX and/or coding POV), ... but even so, it works for the end-user and is thus a successful product.
I'd also venture to say that once you make the mental shift toward validated-learning based development (having customer feedback driving all code changes), it's then highly unlikely that you'll ever come back and refactor code strictly for the purposes of making it better.
]]>You have to strike a balance though, I've seen a 10k line if/then/else chain CGI, that had 0 abstraction. It's often faster to write 0 abstraction, and slower to write more abstraction, though more will get you places in the long run.
In some cases here in perl land I find that we lack the appropriate tools (for all them modules that exist) to do it right quickly.
@Al, interesting thought on why personal projects are hard to complete... I wonder what the solution to it is.
]]>Avoid complex inheritance hierarchies. This is equivalent to only allowing one level of inheritance.
Yes, but then in order to do non-trivial specialisation you have to start inheriting from multiple things, like you would compose multiple roles. And then you run into problems because multiple superclasses can provide the same method – which one do you call? Do you call both? Do the two methods on those two superclasses mean the same thing?
But with roles you do not have this problem. Why? Because roles become part of the class that consumes them, and you cannot compose multiple roles into a class if they provide methods of the same name (you will get an error at compile time) – not without explicitly disambiguating them.
Besides, roles can use other roles (at least they can in Roles::Tiny), so this problem is not avoided by using roles.
Yes, but it is not a problem to have that kind of hierarchy. Why? Same reason: because roles are not inherited by other roles, they are composed into them – again detecting errors at compile time and demanding explicit disambiguation.
Better encapsulation and code re-use. To find if a class does something, you just use the UNIVERSAL->can method. To find if a class has inherited a particular class, use the isa method. This doesn’t seem different to roles.
What this bit mostly means is that because you do not inherit from roles, they can be used like interfaces in Java: you declare a bunch of requirements the consuming class must fulfil, and then someone else can specify the interface instead of a class where they say what type they expect, which works because any class implementing the interface, well, implements the interface. This was Java’s attempt to provide something like multiple inheritance, without providing actual (dangerous) multiple inheritance.
Roles are better because they can include default implementations – with Java-style interfaces, every class conforming to an interface is forced to contain a separate implementation of it, even if it’s boilerplate.
Obviously multiple inheritance doesn’t have that problem. Instead, it has other problems – much much worse problems – problems that roles also don’t have.
So basically:
Think of roles as multiple inheritance without the inheritance – something that gives you the benefits that multiple inheritance promises, except through a different mechanism that works without the indirection of inheritance, and therefore inherently lacks the method dispatch clusterfuck potential of complex MI hierarchies: MI without the diamond problem.
(As a side benefit, because you have two different kinds of entities (classes and roles), you get a clear distinction between concrete entities that can be instantiated (classes) vs conceptual ones that can’t (roles). If you use MI in the same style, everything is just classes, just some of them happen to be abstract, and there’s nothing inherently keeping you from trying to instantiate those, even though it’s useless (and if it works (which it may), a bug).)
]]>One example that I have seen about roles is GuardDog class example: in Dog class hierarchy, and consuming Guard role (which can be consumed by many different unrelated classes).
]]>need more processes now.
david@nio:~/dev/myapp$ ./script/myapp_job_daemon.pl
Found job: 25
Found job: 25
Found job: 25
Processing test_id: 1355
Processing test_id: 1355
Processing test_id: 1355
=)
looking for a job queue now, currently checking out ZeroMQ
]]>