So Apple's veep of hipsterness Phil Schiller has apparently taken a pop at the Great unwashed who hold on to hardware for more than 2 years. Well of course he would. He's a marketing type; he needs to sell stuff. And the market for iPads is in sharp decline. He needs to rally the disciples and get them to lay down cash for an update.
The comment trail in the link above is very telling. Lots of people boasting about 5+ year old macs that are still working fine. The whole point is: that's the problem. Apple used to build good products. We have a Mac mini and MacBook in the family, both at least 4 years old, both still going strong. At least, the hardware is.
The software: not so much. OSX has stagnated. It used to be a great desktop; clean, efficient, and everything just worked. Software developers are a fickle bunch, and we migrated to macs in droves. It was a great, integrated environment that just worked. A refreshing change from the bloatware-ridden Windows ecosystem.
That was then. OSX is very obviously a legacy in Apple's eyes now; iOS is the new cool. IOS is tied to devices, Apple can limit the number of upgrades the user can apply, hey presto built in obsolescence. Ching Ching.
Apple has been dis-investing in OSX for years. We get 'new' features like the Photos app, billed as an enhancement but really just an attempt to cut costs by offering the same thing across iOS and OSX. And it's a dog of a thing. iPhoto might have had limitations, but it was a paragon of functional elegance by comparison. Then there's iTunes. An unholy mess of inscrutable navigation. Or Finder. State of the art file management. Circa 1990. And then there's the inexorable, insidious attempt to lock down the platform to an iOS-style walled garden ecosystem.
It's sad and insulting in equal measure. Apple still builds good hardware. Great hardware in fact. But the software...sucks. I bought a new laptop recently and, for the first time in 10 years, it's not a MacBook. It runs ElementaryOS - a beautiful, fast, functional Linux desktop. Built by people passionate about creating a great desktop experience. Happily, it also runs flawlessly on macs. And it's much faster than OSX, which means I can also breathe new life into our aging macs.
And there lies the crux of the matter for Apple. By disinvesting in their product, they lost a sale and the goodwill of a long term customer. Ironically, in doing so, they extended the life of a couple of aging macs.
So, Mr Schiller, here's a tip. If you want to change the trend in your sales numbers, stop trying to ridicule people. Invest in your software instead: give people a reason to upgrade, not motivation to leave.
Wednesday, March 23, 2016
Thursday, January 28, 2010
A modelling language focused on Relationships?
As with all things, interest in modelling languages is cyclic. In the 90s object orientation was big, leading to the creation of UML. Interest then tailed off, but recently modelling languages have come back in to focus with the rise (again) of domain specific languages.
With each renewed interest comes a renewed search for the one true modelling language - 'one language to rule them all'. It's not as if we're short of candidates: there's MOF (the foundation of UML) and myriad offshoots such as eclipse EMF. Then there are less mainstream alternatives like GOPPRR. More recently there's GModel and a proposal by Atkinson et al.
Each has strengths and weaknesses. UML cemented concepts like classes, objects, binary relationships and subtyping in the mainstream vocabulary, and provided a default graphical syntax. However it's an extremely complex language, not self-consistent and too imprecise for direct interpretation.
GOPPRR provides a much smaller language: easy to understand yet one that's proven useful and workable at the coal face. GModel and Atkinson bring some novel concepts to the space, such as multi-level instantiation.
Though different - significantly so in some respects - all seem to share a common, implicit philosophy. They all appear to arise from first asking the question "what are the useful concepts we need to express?" then secondarily "and how do they relate to each other?".
That's understandable. It's what every book on OO modelling tells us: find the interesting abstractions, and figure out how they fit together. Yet experience says the key to unlocking a problem domain lies in understanding the relationships.
That gives rise to a question: what happens if we focus first on the relationships instead of the things being related? Is there some minimal set of useful relationship types? What would a resultant modelling language look like? And would it actually be any use in practice?
With each renewed interest comes a renewed search for the one true modelling language - 'one language to rule them all'. It's not as if we're short of candidates: there's MOF (the foundation of UML) and myriad offshoots such as eclipse EMF. Then there are less mainstream alternatives like GOPPRR. More recently there's GModel and a proposal by Atkinson et al.
Each has strengths and weaknesses. UML cemented concepts like classes, objects, binary relationships and subtyping in the mainstream vocabulary, and provided a default graphical syntax. However it's an extremely complex language, not self-consistent and too imprecise for direct interpretation.
GOPPRR provides a much smaller language: easy to understand yet one that's proven useful and workable at the coal face. GModel and Atkinson bring some novel concepts to the space, such as multi-level instantiation.
Though different - significantly so in some respects - all seem to share a common, implicit philosophy. They all appear to arise from first asking the question "what are the useful concepts we need to express?" then secondarily "and how do they relate to each other?".
That's understandable. It's what every book on OO modelling tells us: find the interesting abstractions, and figure out how they fit together. Yet experience says the key to unlocking a problem domain lies in understanding the relationships.
That gives rise to a question: what happens if we focus first on the relationships instead of the things being related? Is there some minimal set of useful relationship types? What would a resultant modelling language look like? And would it actually be any use in practice?
Friday, March 27, 2009
Lifted... and let down
Alternative languages on the JVM are very 'in' at the moment, Scala in particular. Almost inevitably, there's a web framework for Scala - Lift. I looked at it briefly a while ago when embarking on a new web application. The 'sales pitch' was compelling, pulling together the best aspects of other frameworks whilst exploiting the power, scalability and safety provided by Scala. However, it was very early in its development with little documentation and few plugins. I decided in the end to use Grails.
Lift has matured somewhat since then so I took another look. I'm still very much taken by the pitch; the framework has matured significantly and the documentation has improved beyond all recognition (i.e. there is now some - including an incomplete but nevertheless good online book. There are also tutorials starting to pop up, so I thought I'd take another look.
Setup is easy, making extensive use of Maven. Next up is creating domain classes - and I'm afraid the first disappointment. Per all web frameworks, Lift comes complete with its own Object Relational Mapping capabilty - named, appropriately, Mapper. The tutorial uses a "ToDo" class as an example, with each item having the following properties: a "done" flag (boolean), a priority value (Integer), a description (String) and a relationship to the Owner of this particular item.
Very simple. In Grails, I'd code this as:
class ToDo {
Boolean done
Integer priority
String description
User owner
}
Pretty intuitive, intentional and economic.
Here's the Lift equivalent from the Tutorial:
class ToDo extends LongKeyedMapper[ToDo] with IdPK {
def getSingleton = ToDo
object done extends MappedBoolean(this)
object owner extends MappedLongForeignKey(this, User)
object priority extends MappedInt(this) {
override def defaultValue = 5
}
object desc extends MappedPoliteString(this, 128)
}
object ToDo extends ToDo with LongKeyedMetaMapper[ToDo]
Wow! Now granted, this is my first introduction to Scala and Lift. But there seems to be an awful lot going on there:
But even allowing for that, why does it have to be so complicated? From my perspective as a Scala and Lift newbie, it almost looks like a case of "we have all this cool stuff at our disposal, lets use it". Hence objects, classes, extension, traits and generics all make it in there.
Now I'm all for using the power of the language. But surely it should be used to both make things more intuitive and improve economy of expression? That certainly doesn't seem to be the case above.
Concept vs. Representation
Scala has some powerful XML handling capabilities, and Lift exploits these to enable tailoring of the way in which elements will be presented. Here's a snippet from one of the tutorials:
override def _toForm: Can[NodeSeq] = {
val funcName = S.mapFunc({s: List[String] => this.setFromAny(s)})
Full(</span><textarea style="font-family: courier new;" name="{funcName}" rows="{textareaRows.toString}" cols="{textareaCols.toString}" id="{fieldId}">{is.toString}</textarea><span style="font-family:courier new;">)
}
Powerful indeed. But wise? First off, it's mixing representation information with the domain class. Or, in MVC terms, mixing part of the View in with the Model. MVC says they should be separate, based on the principles of low coupling and high cohesion. It also complicates the workflow if a graphic designer is doing the UI / layout and a programmer doing the logic. As above, it seems like another case of wilful rather than judicious use of power.
And I'm afraid that's as far as I got. Maybe I should have persevered; Scala's Actor support holds significant promise for efficient scaling. But for now I'm not sold. Grails (and Groovy) may not have some of Scala's power features but the 'user interface' is significantly cleaner. So I'll continue grooving for now.
Lift has matured somewhat since then so I took another look. I'm still very much taken by the pitch; the framework has matured significantly and the documentation has improved beyond all recognition (i.e. there is now some - including an incomplete but nevertheless good online book. There are also tutorials starting to pop up, so I thought I'd take another look.
Setup is easy, making extensive use of Maven. Next up is creating domain classes - and I'm afraid the first disappointment. Per all web frameworks, Lift comes complete with its own Object Relational Mapping capabilty - named, appropriately, Mapper. The tutorial uses a "ToDo" class as an example, with each item having the following properties: a "done" flag (boolean), a priority value (Integer), a description (String) and a relationship to the Owner of this particular item.
Very simple. In Grails, I'd code this as:
class ToDo {
Boolean done
Integer priority
String description
User owner
}
Pretty intuitive, intentional and economic.
Here's the Lift equivalent from the Tutorial:
class ToDo extends LongKeyedMapper[ToDo] with IdPK {
def getSingleton = ToDo
object done extends MappedBoolean(this)
object owner extends MappedLongForeignKey(this, User)
object priority extends MappedInt(this) {
override def defaultValue = 5
}
object desc extends MappedPoliteString(this, 128)
}
object ToDo extends ToDo with LongKeyedMetaMapper[ToDo]
Wow! Now granted, this is my first introduction to Scala and Lift. But there seems to be an awful lot going on there:
- a class and an object with the same name;
- the main ToDo class extends a parameterised type - which is parameterised with ToDo itself
- a singleton ToDo which extends the ToDo class with another type parameterised with ToDo (the object or class? Not sure...)
- a slew of names evidently drawn from the world of relational databases (PK, ForeignKey, MappedXX types).
But even allowing for that, why does it have to be so complicated? From my perspective as a Scala and Lift newbie, it almost looks like a case of "we have all this cool stuff at our disposal, lets use it". Hence objects, classes, extension, traits and generics all make it in there.
Now I'm all for using the power of the language. But surely it should be used to both make things more intuitive and improve economy of expression? That certainly doesn't seem to be the case above.
Concept vs. Representation
Scala has some powerful XML handling capabilities, and Lift exploits these to enable tailoring of the way in which elements will be presented. Here's a snippet from one of the tutorials:
override def _toForm: Can[NodeSeq] = {
val funcName = S.mapFunc({s: List[String] => this.setFromAny(s)})
Full(</span><textarea style="font-family: courier new;" name="{funcName}" rows="{textareaRows.toString}" cols="{textareaCols.toString}" id="{fieldId}">{is.toString}</textarea><span style="font-family:courier new;">)
}
Powerful indeed. But wise? First off, it's mixing representation information with the domain class. Or, in MVC terms, mixing part of the View in with the Model. MVC says they should be separate, based on the principles of low coupling and high cohesion. It also complicates the workflow if a graphic designer is doing the UI / layout and a programmer doing the logic. As above, it seems like another case of wilful rather than judicious use of power.
And I'm afraid that's as far as I got. Maybe I should have persevered; Scala's Actor support holds significant promise for efficient scaling. But for now I'm not sold. Grails (and Groovy) may not have some of Scala's power features but the 'user interface' is significantly cleaner. So I'll continue grooving for now.
Tuesday, March 24, 2009
Relationships are more fun - part II
Trygve Reenskaug and James Coplien have just posted an article questioning the fundamentals of Object Orientation and the Model-View-Controller pattern.
It's interesting (if perhaps a little more wordy than necessary). And I don't agree with all of it (more of that later). But their characterisation of the relationship between the User's mental model and the Object model represented in software is spot on. But perhaps more interesting is their questioning of one of OO's central tenets: that all behaviour belongs in Objects (or Classes in programme language terms).
They assert that, whilst some behaviour does belong within the Object, much doesn't. To use their example: responsibility for increasing or decreasing an Account's balance properly lies with the Account. External parties can ask the Account to perform those operations, but it's up to the Account to do it. That's fully in line with the principles of Abstract Data Types on which OO is founded.
But what about performing a Transfer of funds from one Account to another? Where does that belong? Reenskaug and Coplien assert this doesn't belong with the Account Object - I agree. But that's how it would typically be handled in today's OO languages; so the Account class would typically have a method named "transferTo()", used as follows:
Account src;
Account dest;
src.transferTo(dest, 20.00);
What's wrong with that? From a mental model perspective, it doesn't work. When I think about transferring funds, I don't think about the responsibility lying with either the source or destination account. Sure they're both involved, but neither is responsible.
So where does it belong? In OOP terms we have nowhere else to put it other than in a Class. Reenskaug and Coplien suggest the notion of Interactions as first class entities; however their model still portrays an asymetric pattern (i.e. the responsibility for executing the transfer still lies with either the source or destination objects).
I prefer to think about it lying outside of either. But that doesn't mean the OO model is broken. Maybe it's just the way we use it today. Coming back again to the code example above:
src.transferTo(dest);
Now what happens if we look at this from a relationship perspective? What's the nature of the relationship? It's pretty obvious there's some link between the source and destination accounts - even if it's only transient. Here's an initial view as a UML class diagram?
Both source and destination are instances of Account, so the relationship would have to be recursive on Account. OK. Naming the ends? Easy; one is source, the other destination. Cardinality? Hmm, that's a bit more difficult. A given transfer has exactly one source and one destination account; and any account can be the source and/or target for many transfers. But we can't represent that on our relationship:
Each Account may be the transfer source of one or more other Accounts
Each Account may be the transfer destination of one or more other Accounts
Kinda works, but it looks clumsy; it smells bad. So the cardinality doesn't work and the relationship naming is ugly. What does that tell us? Maybe there's no relationship? Well that's not true. It may be transient but both accounts are undeniably involved in something - otherwise there's no transfer.
Transfer. Hmm. What if, rather than looking at transfer as a verb, we treat it as a noun? What would that mean? Let's rework the model:
Let's examine the relationships again:
Each Transfer must debit exactly one Account
Each Account may be debited in one or more Transfers
An Account need not be debited in any Transfers
Each Transfer must credit exactly one Account
Each Account may be credited in one or more Transfers
An Account need not be credited in any Transfers
That's much better. The cardinality and phrasing capture exactly what we're trying to express - so the relationships look OK. But what about that "Transfer" class? Is it a proper class? Well, what does it mean to be a "proper" class?
It's interesting (if perhaps a little more wordy than necessary). And I don't agree with all of it (more of that later). But their characterisation of the relationship between the User's mental model and the Object model represented in software is spot on. But perhaps more interesting is their questioning of one of OO's central tenets: that all behaviour belongs in Objects (or Classes in programme language terms).
They assert that, whilst some behaviour does belong within the Object, much doesn't. To use their example: responsibility for increasing or decreasing an Account's balance properly lies with the Account. External parties can ask the Account to perform those operations, but it's up to the Account to do it. That's fully in line with the principles of Abstract Data Types on which OO is founded.
But what about performing a Transfer of funds from one Account to another? Where does that belong? Reenskaug and Coplien assert this doesn't belong with the Account Object - I agree. But that's how it would typically be handled in today's OO languages; so the Account class would typically have a method named "transferTo()", used as follows:
Account src;
Account dest;
src.transferTo(dest, 20.00);
What's wrong with that? From a mental model perspective, it doesn't work. When I think about transferring funds, I don't think about the responsibility lying with either the source or destination account. Sure they're both involved, but neither is responsible.
So where does it belong? In OOP terms we have nowhere else to put it other than in a Class. Reenskaug and Coplien suggest the notion of Interactions as first class entities; however their model still portrays an asymetric pattern (i.e. the responsibility for executing the transfer still lies with either the source or destination objects).
I prefer to think about it lying outside of either. But that doesn't mean the OO model is broken. Maybe it's just the way we use it today. Coming back again to the code example above:
src.transferTo(dest);
Now what happens if we look at this from a relationship perspective? What's the nature of the relationship? It's pretty obvious there's some link between the source and destination accounts - even if it's only transient. Here's an initial view as a UML class diagram?
Both source and destination are instances of Account, so the relationship would have to be recursive on Account. OK. Naming the ends? Easy; one is source, the other destination. Cardinality? Hmm, that's a bit more difficult. A given transfer has exactly one source and one destination account; and any account can be the source and/or target for many transfers. But we can't represent that on our relationship:
- 1:1 would only allow each account to be related to a single other: i.e. at most one transfer.
- many:many would only allow each account to be related to every other; but for every pair there could only be one transfer. Again, not what we need.
Each Account may be the transfer source of one or more other Accounts
Each Account may be the transfer destination of one or more other Accounts
Kinda works, but it looks clumsy; it smells bad. So the cardinality doesn't work and the relationship naming is ugly. What does that tell us? Maybe there's no relationship? Well that's not true. It may be transient but both accounts are undeniably involved in something - otherwise there's no transfer.
Transfer. Hmm. What if, rather than looking at transfer as a verb, we treat it as a noun? What would that mean? Let's rework the model:
Let's examine the relationships again:
Each Transfer must debit exactly one Account
Each Account may be debited in one or more Transfers
An Account need not be debited in any Transfers
Each Transfer must credit exactly one Account
Each Account may be credited in one or more Transfers
An Account need not be credited in any Transfers
That's much better. The cardinality and phrasing capture exactly what we're trying to express - so the relationships look OK. But what about that "Transfer" class? Is it a proper class? Well, what does it mean to be a "proper" class?
It's definitely an abstraction of behaviour - the transfer behaviour. What's more, it's a well encapsulated abstraction of behaviour. We are not loading the Account class with the responsibility for executing transfers; it need only respond to "credit" and "debit" requests: appropriate since it maintains the account balance.
What about state? That's OK too. The transfer value again properly belongs with the Transfer. We could also, if required, capture details of the time and success/failure of each transaction. Finally, we could, if required, track the state of each Transfer as it executes ("debiting source account", "crediting destination account", "completed successfully", ...).
There are critics of this approach who disagree with the idea of reification - treating behaviour as things. Rather, objects should be tangible things: Accounts, Customers, Chairs, Telephones, whatever - the things that are things in the real world.
The trouble is that doing so causes the situation that leads to a.transferTo(b). In fact, most of the interesting behaviour doesn't happen in the tangible objects. Transfer is an example of an Interaction object; one that coordinates a protocol amongst other objects. Interaction objects are useful and interesting precisely because they enable clean abstraction of behaviour amongst a number of other objects.
So is the OO model broken? No, not really. We just need to learn to use it properly. Going back to Coplien and Reenskaug, maybe it would help to promote Interactions to first class status, and hence increase focus on collaborations. Key to that is learning to love relationships - they're where all the fun happens.
Relationships are more fun
A number of years ago, I was fortunate enough to work for Steve Mellor's company. I remember vividly the first time I met him; I was very nervous. He was reviewing a domain model that I'd built, and I was amazed at how quickly he found a couple of logical flaws. One thing he said during the review has stuck with me ever since: "the fun is always in the relationships". It struck me as strange at the time; as someone schooled in OO, I had come to view Objects as the things that mattered. Relationships? Just pointers between Objects, right?
Wrong. Very wrong. Experience has fully vindicated Steve's assertion that the interesting stuff really does lie in the relationships, not the objects. In fact, the key to understanding any domain always comes down to formalising the relationships among the domain concepts. So I'm constantly frustrated by models in which relationships are second class citizens, demoted to pointers between objects with perhaps a role name. Something like this:
Why? Because it says nothing about the semantics of the relationship. Why does a Customer have a list of Accounts? Moreover, it says little about the domain - and doesn't prompt discussion or questions about the relationship. Can a customer really have more than one Account? How many Customers is each Account related to? What does the relationship mean when viewed from the Account's perspective anyway?
Steve (and my colleague, close friend and boss at the time Alistair Blair) instilled in me the importance of naming relationships using verb phrases. I've found it extremely valuable, more recently adopting the style recommended in Simsion and Witt's excellent book on Data Modelling. So the model above becomes:
With very little extra effort this provides much more information. We now know the relationship represents "Account Ownership". It doesn't cover being an authorised signatory, or a benefactor, or anything else. Are those things necessary? They may be. The point is the model now prompts that question.
The model also makes assertions about the relationship. An Account must be owned by exactly one Customer. Is that right? What about joint Accounts? Again, the model prompts the question. And so on. We can also generate textual facts (or "business rules") directly and algorithmically from the model, according to the technique described by Simsion and Witt. So we'd get:
Each Person may own one or more Accounts
Each Account must be owned by just one Customer
Furthermore, it tells us something about the behaviour, or interaction between Customer and Account. On first glance it's pretty simple: create a new account, assign it to the owner. Job done. But what happens if the Customer goes away? In typical OO programming terms, that responsibility would lie with the customer:
foreach a in self.accounts:
a.delete()
But why should it belong with the Customer? The required behaviour arises because of the Relationship: it's not intrinsic to the Customer. Another case in point is transfer of ownership. Where does that belong? The Customer? The Account? No. It belongs in the relationship.
So the fun does really happen in the Relationships - thanks Steve and Alistair for enlightening me.
Wrong. Very wrong. Experience has fully vindicated Steve's assertion that the interesting stuff really does lie in the relationships, not the objects. In fact, the key to understanding any domain always comes down to formalising the relationships among the domain concepts. So I'm constantly frustrated by models in which relationships are second class citizens, demoted to pointers between objects with perhaps a role name. Something like this:
Why? Because it says nothing about the semantics of the relationship. Why does a Customer have a list of Accounts? Moreover, it says little about the domain - and doesn't prompt discussion or questions about the relationship. Can a customer really have more than one Account? How many Customers is each Account related to? What does the relationship mean when viewed from the Account's perspective anyway?
Steve (and my colleague, close friend and boss at the time Alistair Blair) instilled in me the importance of naming relationships using verb phrases. I've found it extremely valuable, more recently adopting the style recommended in Simsion and Witt's excellent book on Data Modelling. So the model above becomes:
With very little extra effort this provides much more information. We now know the relationship represents "Account Ownership". It doesn't cover being an authorised signatory, or a benefactor, or anything else. Are those things necessary? They may be. The point is the model now prompts that question.
The model also makes assertions about the relationship. An Account must be owned by exactly one Customer. Is that right? What about joint Accounts? Again, the model prompts the question. And so on. We can also generate textual facts (or "business rules") directly and algorithmically from the model, according to the technique described by Simsion and Witt. So we'd get:
Each Person may own one or more Accounts
Each Account must be owned by just one Customer
Furthermore, it tells us something about the behaviour, or interaction between Customer and Account. On first glance it's pretty simple: create a new account, assign it to the owner. Job done. But what happens if the Customer goes away? In typical OO programming terms, that responsibility would lie with the customer:
foreach a in self.accounts:
a.delete()
But why should it belong with the Customer? The required behaviour arises because of the Relationship: it's not intrinsic to the Customer. Another case in point is transfer of ownership. Where does that belong? The Customer? The Account? No. It belongs in the relationship.
So the fun does really happen in the Relationships - thanks Steve and Alistair for enlightening me.
Monday, February 23, 2009
grails property conundrums
Been writing an application using grails. Very nice framework, but as always there's the usual slew of learning curve conundrums. One in particular had me perplexed (and obsessed) for days.
I needed a class to hold plans; here it is:
------------Plan.groovy---------------------
class Plan {
String name
Date startDate
Date endDate
static hasMany = [tasks : Task]
Integer numDays
public void setNumDays(Integer numDays) {
this.numDays=numDays
this.endDate = this.startDate.plus(this.numDays)
}
}
Simple really; only thing remotely out of the ordinary is the setNumDays() method which automatically calculates the end date from the start date and duration. And here's the corresponding test class:
import java.text.SimpleDateFormat
class PlanTests extends GroovyTestCase {
void testDates() {
def sdf = new SimpleDateFormat("yyyy-MM-dd")
def plan = new Plan(name:"test1",
startDate: sdf.parse("2009-02-22"),
numDays: 7 )
//this shouldn't be necessary!?!
plan.setNumDays(7)
plan.save()
assert plan.name == "test1"
assert plan.startDate == sdf.parse("2009-02-22")
assert plan.numDays == 7
//Fails without call to setNumDays() above
assert plan.endDate == sdf.parse("2009-03-01")
}
}
The strange thing is that without the explicit call to plan.setNumDays() the final assertion fails. This was perplexing. I tried testing as a native groovy class with accompanying test script; contents exactly as above (minus the hasMany in the class and call to plan.save() in the test script). This worked without the explicit setNumDays() call.
I tried various things but couldn't get to the bottom of it - until finally I initialised the startDate field:
------------Plan.groovy---------------------
class Plan {
//...
Date startDate = new Date()
//...
}
With that in place the test works without the explicit setNumDays() call. Initial theory was that numDays was being set before startDate (I had assumed groovy would initialse in the order defined in the new() method call - but maybe not).
However, that doesn't seem to be the case. It doesn't matter which of the date fields is initialised, as long as at least one of them is. But it fails if neither is. Explanation? I'm really not sure...
I needed a class to hold plans; here it is:
------------Plan.groovy---------------------
class Plan {
String name
Date startDate
Date endDate
static hasMany = [tasks : Task]
Integer numDays
public void setNumDays(Integer numDays) {
this.numDays=numDays
this.endDate = this.startDate.plus(this.numDays)
}
}
Simple really; only thing remotely out of the ordinary is the setNumDays() method which automatically calculates the end date from the start date and duration. And here's the corresponding test class:
import java.text.SimpleDateFormat
class PlanTests extends GroovyTestCase {
void testDates() {
def sdf = new SimpleDateFormat("yyyy-MM-dd")
def plan = new Plan(name:"test1",
startDate: sdf.parse("2009-02-22"),
numDays: 7 )
//this shouldn't be necessary!?!
plan.setNumDays(7)
plan.save()
assert plan.name == "test1"
assert plan.startDate == sdf.parse("2009-02-22")
assert plan.numDays == 7
//Fails without call to setNumDays() above
assert plan.endDate == sdf.parse("2009-03-01")
}
}
The strange thing is that without the explicit call to plan.setNumDays() the final assertion fails. This was perplexing. I tried testing as a native groovy class with accompanying test script; contents exactly as above (minus the hasMany in the class and call to plan.save() in the test script). This worked without the explicit setNumDays() call.
I tried various things but couldn't get to the bottom of it - until finally I initialised the startDate field:
------------Plan.groovy---------------------
class Plan {
//...
Date startDate = new Date()
//...
}
With that in place the test works without the explicit setNumDays() call. Initial theory was that numDays was being set before startDate (I had assumed groovy would initialse in the order defined in the new() method call - but maybe not).
However, that doesn't seem to be the case. It doesn't matter which of the date fields is initialised, as long as at least one of them is. But it fails if neither is. Explanation? I'm really not sure...
Thursday, October 9, 2008
Good service = profitable customers
John Seddon has written some wonderful stuff on the application of Lean principles to service organisations. Of particular note is the link among efficient process, customer experience and cost of operation. It's a "cake and eat it" situation: efficient processes make for a good customer experience and low operational costs. Good service also contributes to top line performance: i.e. good service makes for profitable customers.
I had a perfect counter-example with my ISP this week. I'm with Virgin, who are generally OK as long as the system is running. Email has been intermittent recently, and went down again 2 days ago. I checked the online status page - which informed me everything was OK. I called the freephone status line which told the same story. My internet access was up, I could still access gmail, just couldn't get my virgin mail. So, I thought I'd let them know they had a problem. I called the reporting line, navigated the IVR forest, eventually arriving at the usual musical entertainment. After 5 minutes I gave that up and decided to look for electronic submission instead. Sure enough there's a form to submit, the contents of which are mind-numbing. Nevertheless, I persevered.
I got the usual immediate, automated response telling me they'd look in to my query. 2 days later I got the following message:
The link points to another page from whence one may submit a query (quite how virgin.net and virgin media differ I have no idea - and to be frank, don't really care). So it took 2 days to register my complaint, generate a unique ID in their tracking system, and invite me to re-submit my issue. Why? I can't say for sure, but I'd stake the combined value of the UK banking system (or the loose change in my pocket, whichever is greater) on the following:
In other words: "this is a service call. There's no money to be made from service, so we won't really give it much priority".
Of course, had I wanted to upgrade my package, I could have called any of the numbers emblazoned on the web site and spoken to someone instantly (I tried, just to make sure). What Virgin don't seem to get - and they're by no means in a minority - is the influence of service on the customer relationship. Sales are key: cold calling, outbound marketing, lead generation systems, the list goes on. Service? That's a necessary inconvenience.
How wrong they are. Good service is a - maybe even the - key factor in a customer's propensity to terminate a relationship. Companies would save a lot more money by keeping their existing customers instead of spending vast resources attracting new ones. I was reasonably happy with Virgin before this debacle. I'm now researching alternative ISPs.
Update 3/2/09: I'm no longer with Virgin. I found another ISP who responded quickly and personally to my initial inquiries and were knowledgeable and professional. They managed the swap without fuss or problem and have been similarly responsive with a couple of service-related queries since. So good in fact, I recommended them to a friend who has similarly just moved there - from Virgin. Enough said. What's more, they're more expensive than Virgin were - considerably more than the reduced rate the Virgin adviser promised me if I stayed put.
(And final joy of joys: BBC iPlayer now works flawlessly. Wall to wall Top Gear!)
I had a perfect counter-example with my ISP this week. I'm with Virgin, who are generally OK as long as the system is running. Email has been intermittent recently, and went down again 2 days ago. I checked the online status page - which informed me everything was OK. I called the freephone status line which told the same story. My internet access was up, I could still access gmail, just couldn't get my virgin mail. So, I thought I'd let them know they had a problem. I called the reporting line, navigated the IVR forest, eventually arriving at the usual musical entertainment. After 5 minutes I gave that up and decided to look for electronic submission instead. Sure enough there's a form to submit, the contents of which are mind-numbing. Nevertheless, I persevered.
I got the usual immediate, automated response telling me they'd look in to my query. 2 days later I got the following message:
Thanks for getting in touch with the Virgin Media Support team.
We're sorry that you are having problems with your e-mails. To help with this we need to get your issue resolved by our colleagues at Virgin.net
In order for your support query to be dealt with efficiently, could you please click on the following link:
http://www.virgin.net/customers/contactus/
This will ensure that the correct team will receive your form.
The link points to another page from whence one may submit a query (quite how virgin.net and virgin media differ I have no idea - and to be frank, don't really care). So it took 2 days to register my complaint, generate a unique ID in their tracking system, and invite me to re-submit my issue. Why? I can't say for sure, but I'd stake the combined value of the UK banking system (or the loose change in my pocket, whichever is greater) on the following:
- Virgin's customer service team have targets for responding to customer queries (the website states 48 hours)
- My query was edging towards the target, so someone (or something) decided a response was needed.
- So they sent me another stock email that didn't solve my problem, but hey it met the target - so that's good, right?
In other words: "this is a service call. There's no money to be made from service, so we won't really give it much priority".
Of course, had I wanted to upgrade my package, I could have called any of the numbers emblazoned on the web site and spoken to someone instantly (I tried, just to make sure). What Virgin don't seem to get - and they're by no means in a minority - is the influence of service on the customer relationship. Sales are key: cold calling, outbound marketing, lead generation systems, the list goes on. Service? That's a necessary inconvenience.
How wrong they are. Good service is a - maybe even the - key factor in a customer's propensity to terminate a relationship. Companies would save a lot more money by keeping their existing customers instead of spending vast resources attracting new ones. I was reasonably happy with Virgin before this debacle. I'm now researching alternative ISPs.
Update 3/2/09: I'm no longer with Virgin. I found another ISP who responded quickly and personally to my initial inquiries and were knowledgeable and professional. They managed the swap without fuss or problem and have been similarly responsive with a couple of service-related queries since. So good in fact, I recommended them to a friend who has similarly just moved there - from Virgin. Enough said. What's more, they're more expensive than Virgin were - considerably more than the reduced rate the Virgin adviser promised me if I stayed put.
(And final joy of joys: BBC iPlayer now works flawlessly. Wall to wall Top Gear!)
Subscribe to:
Posts (Atom)