My current project integrates with the Microsoft Dynamics CRM system for managing customer records. We already spiked out authenticating and our next step was attempting to update a record via its “REST Endpoint“.
On retrieving a record, you follow the OData style for finding something of relevant. Updating a record is interesting as we only want to send the fields that need to change remotely. Looking at their sample code, you need to:
X-HTTP-Method to MERGEAccept to application/json and the Content-Type to application/json; charset=utf-8Our end point for a contact looked something like:
https://crm.thekua.com/xrmservices/2011/organizationdata.svc/ContactSet(guid'867dc3f2-909e-e111-9912-0050569c2d72')
.
Updating simple fields off a contact is easy. We post something like:
{
"EMailAddress1":"spike.jones4@gmail.com",
"MobilePhone":"33335"
}
We receive a HTTP 204 (No Content) on a success. Posting an invalid attribute (i.e. one that does not exist on the ContactSet such as “EMailAddreXXXs1″) results in a HTTP 400 (Bad request) and a nice description about what’s wrong. You will also get a HTTP 400 if you post the wrong datatype such as sending a string where they expect a number. If you pass invalid values (but of the correct datatype) for a field, your response is a HTTP 500 with a message like The value of 'gendercode' on record of type 'contact' is outside the valid range."
Updating Option Set Values
Updating simple datatypes is easy and obvious from a JSON point of view. You have a couple of complex datatypes, such as the standard GenderCode. When you query for a record, you get back something that looks like
{"GenderCode"=>{"__metadata"=>{"type"=>"Microsoft.Crm.Sdk.Data.Services.OptionSetValue"}, "Value"=>1}}
To update something like this, you need to send a nested JSON object. The example follows
{
"EMailAddress1":"spike.jones4@gmail.com",
"MobilePhone":"33335",
"GenderCode": {
"Value" : "1"
}
}
A HTTP 204 (No Content) response indicates a success. Viola!
In the comments on my post about generating random numbers to test a function David Turner suggested that this was exactly the use case for which QuickCheck was intended for so I’ve been learning a bit more about that this week.
I started with a simple property to check that the brute force (bf) and divide and conquer (dc) versions of the algorithm returned the same result, assuming that there were enough values in the list to have a closest pair:
prop_closest_pairs xs = length xs >= 2 ==> dcClosest xs == (fromJust $ bfClosest xs)
I could then run that as follows:
> import QuickCheck.Test > quickCheck(prop_closest_pairs :: [(Double, Double)] -> Property)
It failed pretty quickly because the bf and dc versions of the algorithm sometimes return the pairs in a different order.
e.g. bf may say the closest pair is (2.0, 0.0), (2.1, 1.1) while dc says it’s (2.1, 1.1), (2.0, 0.0) which will lead to the quick check property failing because those values aren’t equal:
> ((2.0, 0.0), (2.1, 1.1)) == ((2.1, 1.1), (2.0, 0.0)) False
The best way I could think of to get around this problem was to create a type to represent a pair of points and then write a custom equality operator.
I initially ended up with the following:
type Point a = (a, a) data Pair a = P (Point a) (Point a)
instance Eq (Pair a) where P a b == P c d = a == c && b == d || a == d && b == c
Which didn’t actually compile:
qc_test.hs:41:58:
No instance for (Eq a)
arising from a use of `=='
In the second argument of `(&&)', namely `b == c'
In the second argument of `(||)', namely `a == d && b == c'
In the expression: a == c && b == d || a == d && b == c
The problem is that while we’ve made Pair an instance of the Equality type class there’s no guarantee that the value contained inside it is an instance of the Equality type class which means we might not be able to compare its values.
We need to add a class constraint to make sure that the value inside P is a part of Eq:
instance (Eq a) => Eq (Pair a) where P a b == P c d = a == c && b == d || a == d && b == c
Now we’re saying that we want to make Pair an instance of the Equality type class but only when the value that Pair contains is already an instance of the Equality type class.
In this case we’re just storing pairs of doubles inside the Pair so it will work fine.
Now if we compare the two points from above we’ll see that they’re equal:
> P (2.0, 0.0) (2.1, 1.1) == P (2.1, 1.1) (2.0, 0.0) True
I had to go and change the existing code to make use of this new but it didn’t take more than 5-10 minutes to do that.
Today, I feel like a massive RANT.
Although I’d rather book myself in for a root canal than get involved in creating proposals or responding to RFPs, I do find myself in this position frequently these days and I’ve yet to develop a suitably sustainable perspective on these things to make them enjoyable. Hell, forget enjoyable as a goal – I’d settle for a beige sort of feeling rather than the darker colours I tend to find myself wallowing in during those times.
And here’s why – proposals suck. The act of building them… sucks. The process they are a part of… sucks.
Why? Well, let me give my completely objective and not-at-all bitter take on how this stuff works.
Step 1: Company A wants some stuff done. They decide to go to market for whatever reason. Their governance mechanisms demand some form of RFP process to ensure fairness and transparency, etc, etc (hint: it doesn’t and it isn’t, but that’s another story entirely)
Step 2: Company A taps the shoulder of some poor schmuck(s) to write the documentation describing the work to be done. This document is usually 40% boilerplate with the rest being the gristle of the problem to be solved. Generally the authors of these documents are domain experts so understand the problem domain quite well. However using domain experts to explain complex problems to people who don’t share that expertise also sucks because:
Step 3: Documentation is distributed to interested parties for response by a certain time. Some RFP processes include open Q&A sessions with all the potential respondees which inevitably turn into variations of pissing contents (i.e., who can ask the questions that intimidates the other parties in terms of specificity, detail, prior knowledge, etc) or exercises in studied banality (i.e., don’t ask anything publicly which might reveal how we’re thinking about the solution).
Step 4: Respondees digest documentation and subsequently devise solution, estimates, timelines, costs, staffing models, risks, etc., etc. Now this is where things get really interesting because I’m often part of the ”devise solution plus estimates” side of things and a whole new circle of Hell must be navigated to survive this phase. The natural tensions are:
Step 5: Respondees send response to Company A who now have the unfortunate task of reading, understanding, comparing and ranking n proposals. Sometimes there is a shortlist process and you get a second bite at the cherry and refine your response. Rarely is there any formal feedback process from Company A back to the respondees apart from the tersest of communications saying you have been successful or otherwise.
If you’re unsuccessful, I believe the required behaviour is to either justify the response by saying you didn’t want the work in the first place or rationalise the decision by saying that whoever won did so by lowballing the price with a strategy to change manage their way to profitability.
If you’re successful, then the once the project gets started, the vast majority of the assumptions you made to justify your estimates will be immediately or eventually invalidated rendering the basis of your estimates and costings null and void… this part doesn’t suck so much
Next proposal – BRING IT ON!
Last week I completely re-worked the short presentation I share with nonprofit clients who want to get others in their teams excited about what agile practices can do for them. 
If you’re a staff member or volunteer on a team, this stuff can be so very helpful. Check out the presentation: Agile – What is it and why should I care?. And feel free to ask questions. Or steal it!
One of the big problems with successfully executing projects is that while we know projects are very different from each other, we often manage them and measure their success in the same way. Think for a moment about the oft quoted Standish reports in which project success is measured on the traditional iron triangle basis of meeting scope, schedule, and cost plans. In their scheme, all projects, of any type, are successful or not based on the same criteria.
A friend of mine worked on a project recently where the client said, “I have a fuzzy vision of what we want. I don’t have any idea what the detail requirements should be. I need results fast.” The client even refused to participate in a story identification process, leaving that up to the development team to experiment. Managing this project against traditional measures of scope, schedule, and cost would damage any chance of success. This is a different kind of project. The team evolved the product from a vision and evolutionary learning and the client was very pleased with the results, because he understood it was a different type of project.
In Agile Project Management I wrote about assigning an Exploration Factor (EF) to each project (or release of a product). The EF attempts to identify a level of uncertainty and risk for a project by looking at both technology and requirements. Technology can run the gamut from “well-known” to “bleeding edge” and requirements from “stable” to “erratic.” Combining the two factors yields EFs (see figure) of from 1 (very low uncertainty) to 10 (very high uncertainty and risk). EF 1 and EF 10 projects are very different—they need to be managed differently and they need to be assessed differently. For example, if the requirements are uncertain, measuring progress against a scope plan is ridiculous. This doesn’t mean the project is uncontrollable, just that we have to control it differently.
So, before we think about how to measure success on projects with high EFs, we need to understand what makes these projects successful. The pressures on high EF projects are usually uncertainty (about either requirements, technology, or both) and speed. Typically high EF projects are also strategic, customer facing, web or mobile, etc.—all aspects that lead to uncertainty. Sponsors want them done quickly. So how are projects like this managed successfully? By using a combination of a well-articulated vision, quick iterations, evolving functionality to a minimal viable product and beyond, being adaptable to learning from customers as the product evolves, focusing on value delivery, and time boxing.
One important aspect of controlling high EF projects is to view scope, schedule, and cost as constraints—not plans. Because the project requirements are not known it is often difficult to estimate time. However, given the lack of specificity the team needs constraints, for example a time box of 3 months that limits exposure. At the end of this timeframe the project sponsor can evaluate the value delivered, the questions answered, and then decide to invest in the next increment or not. The question to be asked at every iteration and release is, “What is keeping us from deploying this product now?” This is very different from the normal progress question of “Have we developed all the planned scope yet?”
Conversely, a very low EF project, say a 1 or 2, could be managed with scope questions because the requirements are knowable in the beginning (or at least people think they are even though we know they are often wrong). I might add that software development projects are rarely low EF projects.
The bottom line is:
TLDR Version: User Stories Applied is an excellent resource for anyone who wants to gain a better understanding of the ideas behind user stories. The book has very good examples and guidelines on writing better user stories and integrating them into your development process.
User Stories Applied is a book that has often been mentioned to me in the six months I’ve been at ThoughtWorks and over the last two weeks I finally managed to get my hands on a copy. It’s not a very long book (230 pages) and it took me about 3 plane trips to finish it.
The GoodThis book provides a very good outline of how user stories work and the intent behind them. Even though I work with user stories every day I found some of the details quite refreshing – especially how user stories are not meant to contain the details behind features, but rather to be a medium for us to start discussions around features.
I particularly liked the discussion on how to write good user stories and the guidance on when stories are too small or too large. This is an area where I think experience really helps, but the pointers in this book are a great starting point.
The BadEvery chapter in the book ends with a summary and an outline on how the chapter influences the different roles on a development team. I’m not a big fan of this kind of writing – it reminds me of the textbooks I had at school and it doesn’t seem to really add any value.
The second half of the book seems to focus on comparing user stories to other approaches. I felt like the author was trying to defend user stories, but the arguments presented were more focused on the shortcomings of the other approaches rather than the advantages of user stories. Having said that, this can be very helpful if you’re trying to convince a client to use this practice on a project.
InterestingI was rather intrigued by the difference between how the author describes user stories and what I’m used to. The author advocates that user stories should contain very little detail and the details should be discussed when the story is developed. I’m used to user stories containing most of the detail around a story – in fact stories will usually be pushed back if the details are left out.
I think using user stories as a placeholder for conversations around the details can work really well, but on projects with a large amount of developers (I’m guessing more than 8 developers) the interaction with the user will quickly become a bottleneck. User stories can therefore also work very well when they contain all the necessary details which are analysed and discussed in full before development is started – it’s not a massive difference, but on large teams it can certainly have a massive effect.
User Stories Applied as an excellent resource for anyone who wants to gain a better understanding of the ideas behind user stories. The book has very good examples and guidelines on writing better user stories and integrating them into your development process.
I have come across too many people who are either not willing to blog, or do not see the point. This blog post is for them.
(Recently, Elena Yatzeck posted on this subject as well.)
I recently read a one-liner “I see I scorn, I do I regret, I blog to not forget”. (Usually I am turned off by one liners intending to pack everything including the reason for life in minimum words, but this one I like because it is funny enough.)
This above quote is enough motivation to keep me going on my blogging. In the day and age of #fb and #RT it is difficult to get people excited about the value of details and being able to express in elaboration. Blogs come to the rescue.
I experience this loss of details quite a bit. Recently on my M$ platform based assignment I forgot to blog what we did to make our .NET application development painless. Now when I try to recollect some lessons I learnt I have to depend on my memory which is sloppy about details. I blog to ensure that I have less of these situations and that I can use my experience to my advantage – also maybe readers of my blog will benefit as well.
I would like to encourage more blogging so that I can benefit from your knowledge and learnings.
Here are a few reasons you should consider blogging:
How and where to blog?
What are you waiting for? Go write something.
Does your team feel safe expressing every idea that’s relevant to your work?
Imagine your team sitting around a table. People either express what they’re thinking (putting their ideas, like cards, on the table) or they don’t (protecting their ideas by hiding them under the table.
When we work in teams, we most likely only talk about what’s on the table – that is, the issues, feelings and ideas that people have expressed and can talk about. It works great, as long as our team feels comfortable enough to get their thoughts out. When people fear the outcome of saying something, though, they start to push their thoughts under the table – this keeps them and their ideas safe from criticism, ridicule, or embarrassment.
The things we keep under the table have just as much bearing on our ability to participate in our team’s work as they stuff we do talk about, though. Imagine a team conversation when…
How well is this group really going to be able to pay attention, listen and give opinions openly?
You’ve probably been at this team meeting before. It’s easy to spot – the group goes silent or listless… or, on the other end of the spectrum, gets confrontational with no real purpose. What do you do?
Lead by example, if you can – bring up something you’re keeping under the table that stops you from participating fully. Talk about how it’s holding you back, and ask others if they have similar thoughts to discuss. Ask your team members some powerful questions.
What hopes do you have for the team that you hesitate to express?
What needs to change so you feel safe expressing those hopes?
What issues, ideas or feelings are we avoiding as a group?
What could we gain by talking about these things?
What is holding us back in this conversation?
You can do this formally or informally. You probably know what makes sense for your own team members. What else has worked for your teams when you find yourselves stuck like this?
The circumstances of our lives may matter less than how we see them, says Rory Sutherland. At TEDxAthens, he makes a compelling case for how reframing is the key to happiness.
I was out shooting today, putting the x-pro through its paces. No real purpose – meandering around the Portobello Road, shooting stall keepers and tourists alike. I turn for home, walking alongside the westway, when a cyclist zips past. I see him, heading towards me, bring the camera up, track him and *click*. Enough time for one shot, and it’s perfect. He is captured on the top right, pin sharp, looking straight into the lens. The background is beautifully blurred. Perfect shot.
I walk on, and get a tap on my shoulder. I turn around to see the cyclist.
“Can you delete that, please?”.
And I do.
Walking back, I think idly “I could probably recover that…”. Legally, I’ve done nothing wrong. And it is a great shot – one of the best I think I’ve taken. But I sigh, and know I won’t. At least this is one shot I can’t claim to have lost due to the Fuji x-pro’s AF.
As we kept discussing about the iteration, we started moving story stages onto iteration lifecycle. Also started building the events as shown in the picture below. Session took an hour to run.
自从迈出了ThoughtWorks校园行的第一步,西电的校园活动就一次次进行下来,我们讲了如何写代码,讲了完整的开发过程,后续的一系列校园技术讲座也已经排上了日程。
其实,单以效果而言,技术讲座所带来的影响是有限的,对于参加讲座的学生来说,除了知道很多新名词、新做法,多半也就是看个热闹。直接动手实践,才是一个更好的做法。
感谢我们优秀的市场MM,她为我们打开了一片新天地。感谢西安交通大学软件学院的领导具备的卓越眼光,为他们的学生提供了一个接触外面世界的机会。
于是,我们有了一个新的机会,在西安交通大学开设一门软件开发的课程。
这门课,我们称之为“现代软件开发”,其目的就是为了告诉同学们,不同于传统方式的开发方法。其实,我们本可以叫敏捷软件开发方法,但我着实不喜欢这个名字,因为这已不是我追求的东西,我只想把一些好的东西告诉同学们,所以,选了一个不容易过时的名字。
我们是这样设计的这门课,采用上下半场的方式运作。上半场,我们会介绍一些基本的开发方法,比如整洁代码,比如重构,比如自动化等等。而下半场,则完全是实践。我们选取了一个项目,按照我们的方式运作了一个项目,我们的同事会与同学们结对,让他们直接体会最原汁原味的ThoughtWorks开发方式。
就在这个周末,这个系列课程终于迈出了第一步。
第一次课基本上算是一个课程介绍,让为有兴趣选修这门课的同学了解这门课,以及我们的上课方式。我们讲了公司里如何做软件,还演示了结对开发和TDD。
第二步就这样迈了出来,在接下来的一段时间里,虽然要牺牲一些业余时间,但这也是一个很好的尝试,让我们的课程更加系统化,也给了我们的同事一个很好的锻炼机会。
我们的同事在课程的结尾送给同学们一句话,与其周末逛街打DOTA,还不如来写写代码。嗯,就是这样。
When I was looking over my solution to the closest pairs algorithm which I wrote last week I realised there there were quite a few if statements, something I haven’t seen in other Haskell code I’ve read.
This is the initial version that I wrote:
dcClosest :: (Ord a, Floating a) => [Point a] -> (Point a, Point a)
dcClosest pairs
if length pairs <= 3 then = fromJust $ bfClosest pairs
else
foldl (\closest (p1:p2:_) -> if distance (p1, p2) < distance closest then (p1, p2) else closest)
closestPair
(windowed 2 pairsWithinMinimumDelta)
where sortedByX = sortBy compare pairs
(leftByX:rightByX:_) = chunk (length sortedByX `div` 2) sortedByX
closestPair = if distance closestLeftPair < distance closestRightPair then closestLeftPair else closestRightPair
where closestLeftPair = dcClosest leftByX
closestRightPair = dcClosest rightByX
pairsWithinMinimumDelta = sortBy (compare `on` snd) $ filter withinMinimumDelta sortedByX
where withinMinimumDelta (x, _) = abs (xMidPoint - x) <= distance closestPair
where (xMidPoint, _) = last leftByX
We can remove the first if statement which checks the length of the list and replace it with pattern matching code like so:
dcClosest :: (Ord a, Floating a) => [Point a] -> (Point a, Point a)
dcClosest pairs
| length pairs <= 3 = fromJust $ bfClosest pairs
| otherwise = foldl (\closest (p1:p2:_) -> if distance (p1, p2) < distance closest then (p1, p2) else closest)
closestPair
(windowed 2 pairsWithinMinimumDelta)
...
We can also get rid of the if statement inside the first argument passed to ‘foldl’ and replace it with a call to ‘minimumBy’:
dcClosest :: (Ord a, Floating a) => [Point a] -> (Point a, Point a)
dcClosest pairs
| length pairs <= 3 = fromJust $ bfClosest pairs
| otherwise = foldl (\closest (p1:p2:_) -> minimumBy (compare `on` distance) [closest, (p1, p2)])
closestPair
(windowed 2 pairsWithinMinimumDelta)
...
We can do the same to replace the if statement where we work out the closestPair which results in this final version of the code:
dcClosest :: (Ord a, Floating a) => [Point a] -> (Point a, Point a)
dcClosest pairs
| length pairs <= 3 = fromJust $ bfClosest pairs
| otherwise = foldl (\closest (p1:p2:_) -> minimumBy (compare `on` distance) [closest, (p1, p2)])
closestPair
(windowed 2 pairsWithinMinimumDelta)
where sortedByX = sortBy compare pairs
(leftByX:rightByX:_) = chunk (length sortedByX `div` 2) sortedByX
closestPair = minimumBy (compare `on` distance) [closestLeftPair, closestRightPair]
where closestLeftPair = dcClosest leftByX
closestRightPair = dcClosest rightByX
pairsWithinMinimumDelta = sortBy (compare `on` snd) $ filter withinMinimumDelta sortedByX
where withinMinimumDelta (x, _) = abs (xMidPoint - x) <= distance closestPair
where (xMidPoint, _) = last leftByX
It takes up marginally less space and I think the change to use pattern matching on the length of ‘pairs’ makes the biggest difference as the code is now lined up at the same level of indentation.
The other changes would have more of an impact if there were more than 2 things being compared – right now I think either of the versions of the code are equally readable.
As I mentioned in a blog post about a week ago I decided to restructure the ThoughtWorks graph I’ve modelled in neo4j so that I could explicitly model projects and clients.
As a result I had to update a traversal I’d written for finding the shortest path between two people in the graph.
The original traversal query I had was really simple because I had a direct connection between the people nodes:

neo = Neography::Rest.new
paths = neo.get_paths(start_node,
destination_node,
{ "type" => "colleagues" },
depth = 3,
algorithm = "shortestPath")
.map { |x| x["nodes"] }
.uniq
paths.map { |p| p.map { |node| neo.get_node_properties(node, "name")["name"] } }
I changed the way the graph was modelled so that you needed to follow a ‘worked_on’ relationship to a project in order to go between people:

In the first version I’d written some pre processing code in Ruby to check whether or not people worked on the project at the same time before creating the relationship between the nodes.
It wasn’t possible to do that with the new structure since I was working out if there was a colleagues relationship dynamically.
I therefore added a ‘start_date’ and ‘end_date’ property to the ‘worked_on’ relationship between a person node and project node so that I’d be able to take it into account when traversing the graph.]
I initially thought it would be possible to do this using cypher and wrote the following query:
start_node_id = neo.send(:get_id, start_node)
destination_node_id = neo.send(:get_id, destination_node)
query = " START a=node(#{start_node_id}), x=node(#{destination_node_id})"
query << " MATCH p = allShortestPaths( a-[:worked_on*]-x )"
query << " RETURN p, extract(person in nodes(p) : person.name)"
paths = neo.execute_query(query)
I wasn’t sure how to do the filtering on ‘start_date’ and ‘end_date’ and Andres pointed out that it’s not actually currently possible to take relationship properties into account when traversing a graph with cypher so we need to do the filtering on ‘start_date’ and ‘end_date’ in code.
My first attempt to do that looked like this:
paths = neo.get_paths(node1, node2, { "type" => "worked_on", "direction" => "all" },
depth = 5, algorithm = "shortestPath").uniq
matching = paths.select do |row|
relationshipPairs = row["relationships"].each_slice(2).to_a
relationshipPairs.all? do |pair|
r1 = neo.get_relationship(pair[0])["data"]
r2 = neo.get_relationship(pair[1])["data"]
r1["start_date"] <= r2["end_date"] && r1["end_date"] >= r2["start_date"]
end
end
The problem with this approach is that it’s really slow due to the fact that I’m pulling back every relationship back in order to check the start and end dates.
Michael Hunger suggested an alternative approach where I still used a cypher query but returned the relationships instead of just the nodes:
start_node_id = neo.send(:get_id, start_node)
destination_node_id = neo.send(:get_id, destination_node)
query = " START a=node(#{start_node_id}), x=node(#{destination_node_id})"
query << " MATCH p = allShortestPaths( a-[:worked_on*]-x )"
query << " RETURN p, rels(p), extract(person in nodes(p) : person.name)"
paths = neo.execute_query(query)
matching = paths["data"].select do |row|
relationship_pairs = row[1].each_slice(2).to_a
relationship_pairs.all? do |pair|
r1 = pair[0]["data"]
r2 = pair[1]["data"]
r1["start_date"] <= r2["end_date"] && r1["end_date"] >= r2["start_date"]
end
end
matching.map { |x| x[2].each_with_index.select { |x,idx| idx.even? }.map(&:first) }.uniq
This approach mostly works although it runs into problems in the scenario where two people have worked on the same project but not at the same time.
In that scenario the above code will return the relationship between them but it will then be filtered out by the start date/end date logic which means we won’t see a shortest path between those nodes.
I’m not sure how to solve that problem so for the moment I’m going to take the Josh Adell’s suggestion to keep the ‘colleagues’ relationship between nodes and use that relationship for the shortest path traversal.
The ‘worked_on’ relationship is still useful for other things that I want to do but not this particular one.
You're a Rails programmer. You've developed your site and have a good set of features that you're delivering to your users. You use RSpec to test your application and have Cucumbers pushing WebDriver tests. You may even be doing some cool process things like continuous deployment. But, do you write Ruby or do you write Rails?
Rails is a great web framework that has helped many teams deliver business value in a very short amount of time. It helped to create the mantra of Opinionated Software that exists in the Ruby community and has even started to trickle down to users (for the benefit of all, in my humble opinion). Rails has also been a great leader in the area of testing and craftsmanship in the greater software practice.
That being said, Rails has become a crutch for many people (as all frameworks tend to do). It encourages a very flat world of MVC where the models can go get themselves out of a persistent store and controllers and views can access everything.
Name the number of classes in your code that do not inherit (directly or indirectly) from ActiveRecord::Base (or, in my case, Mongoid::Document), ActionView::Base, or ActiveController::Base. Is it more than zero? Is it a significant percentage of your application? Is it most of your application?
At a certain point your web application will become more than a CRUD web server where users manipulate objects in a database. Let us take for example GitHub. When you first log in to GitHub, you are sent to your dashboard. I'm going to write some bad code:
class DashboardController < ApplicationController
def index
# we're using devise and the user is logged in (checked in ApplicationController)
end
end
.dashboard
.news
- current_user.alerts.limit(20).each do |alert|
.alert
.type= alert.type
.header= alert.commit? ? commit_header(alert) : pull_request_header(alert)
.message
.committer_image= alert.commit.committer.gravatar
.commit_mesasge= alert.commit.message
Excuse the incompleteness of the example but this is enough to illustrate. This is a Bad Thing⢠because we are doing many N+1 queries against our SQL database. Easy enough to fix. We should pre-fetch all of our dependencies. I am going to use a more explicit way instead of trying to let the ORM do my work for me, again for illustration.
class DashboardController < ApplicationController
def index
@alerts = current_user.alerts.limit(20)
@commits = Commit.where(:_id.in => @alerts.map(&:commit_id)).to_a # using Mongoid here
@committers = User.where(:_id.in => @commits.map(&:committer_id)).to_a # using Mongoid here
end
end
.dashboard
.news
- @alerts.each do |alert|
.alert
.type= alert.type
.header= alert.commit? ? commit_header(alert) : pull_request_header(alert)
.message
.committer_image= alert.commit.committer.gravatar
.commit_mesasge= alert.commit.message
Alright, so now we've taken care of the N+1 query issue. However, I'm loading a ton of unnecessary data from the database, building it into objects in member and then garbage collecting it later down the road. We're GitHub and we cannot afford this luxury, so we cut the fat (always test that this sort of optimization is necessary though!!).
class DashboardController < ApplicationController
def index
@alerts = current_user.alerts.only(:type, :commit_id).limit(20)
@commits = Commit.where(:_id => @alerts.map(&:commit_id)).only(:message, :committer_id).to_a # using Mongoid here
@committers = User.where(:_id => @commits.map(&:committer_id)).only(:gravatar).to_a # using Mongoid here
end
end
.dashboard
.news
- @alerts.each do |alert|
.alert
.type= alert.type
.header= alert.commit? ? commit_header(alert) : pull_request_header(alert)
.message
.committer_image= alert.commit.committer.gravatar
.commit_mesasge= alert.commit.message
And, we're starting to notice that our .header= HAML line is really big. We could pull it into another helper method. Or, we could pull it up into the model itself. Which is it? View logic or model logic? For sake of my example, let's put it in the model.
class Alert
include Mongoid::Document
def header
commit? ? commit_header : pull_request_header
end
end
And we notice that our controller method is getting big-ish. We should move that code to the model, as well, right?
class Alert
def self.alerts_for_dashboard(current_user)
alerts = current_user.alerts.only(:type, :commit_id).limit(20)
commits = Commit.where(:_id => @alerts.map(&:commit_id)).only(:message, :committer_id).to_a # using Mongoid here
committers = User.where(:_id => @commits.map(&:committer_id)).only(:gravatar).to_a # using Mongoid here
alerts
end
end
And this is the state in which we push the code to production and call it a day.
We forgot about using Ruby itself at some point in learning how to develop Rails applications. I would contend that the model is the place for neither of these peices of code. The header code is not part of the model but is instead presentation logic.
On helpers: helpers are the default way to encapsulate presentation logic in Rails. There is nothing wrong with helpers themselves but they start to get out of hand when you start to pull in many different objects, cascade helper calls and use helpers from many different helper files. When that starts to happen, we need to move away from helpers to a more robust method of handling presentation logic.
This dashboard view should probably have something called an AlertPresenter (for another pattern, look at Avdi's Exhibit Pattern). The AlertPresenter would look something like this
class AlertPresenter # look ma, I'm an Object
def initialize(alert) # look dad, I'm using explicit constructor arguments!
@alert = alert
end
def header
@alert.commit? ? commit_header : pull_request_header
end
end
This would encapsulate all of the logic needed to write the "header" of the GitHub dashboard alert in one place. If I ever needed to change what that looks like, I know exactly where to look. Another side benefit of this class is that I do not need to load Rails to run this test. That means the test can run fast. Very fast.
The other peice of code is not presentation logic, but persistance logic. Because of ActiveRecord, we have started to conflate persistance and domain logic in our codebases. This is a bad trend. It creates confusion and a dangerous amount of coupling in our code. While I could go on about the benefits of a DataMapper pattern over the ActiveRecord pattern, I will just show you what we could have done with our code to not bind ourselves so tightly with AR.
Instead of defining a class method on Alert, we could create an AlertService that accesses these alerts for us. At this point, it really is just putting the code into another file but it does have benefits. First, its not Rails and doesn't actually require Rails to test. It makes the finding of alerts for the dashboard logic all easy to find and located in a different location. And, it provides a way for us to change how we access it. If, for example, we were to implement CQRS in our application, the Service would be the place to do it. Or, if we decide that we need to implement an HTTP service on the backend that serves us the objects instead of direct access to the database, the service layer can be a point of abstraction for persistance logic. There are more benefits if we take the service/repository pattern further.
class AlertService
def self.alerts_for_dashboard(current_user)
alerts = current_user.alerts.only(:type, :commit_id).limit(20)
commits = Commit.where(:_id => @alerts.map(&:commit_id)).only(:message, :committer_id).to_a # using Mongoid here
committers = User.where(:_id => @commits.map(&:committer_id)).only(:gravatar).to_a # using Mongoid here
alerts
end
end
So, in this very small example, we have decided to create a couple of Ruby objects that make our application more flexible, testable and cleaner.
I challenge Rails developers to develop a sense of cleanliness of their code. We have a huge commitment to tests and working software. We have a commitment to opinionated software that relies on convention over configuration and to give back to the open source community. It think us Rails developers can learn to appreciate the cleanliness of code. I would define cleanliness as follows:
I've only talked about a couple of the things that make clean Rails code possible (Presenter and [Persistance]Service). I hope to talk about other things in the near future.
自从开始做产品之后,我就成为了产品运营人员的一部分。每天,我与行政收到同样多的邮件。出差请求,感谢,抱怨,我都一一看在眼里,记在心里。得益于之前出了无数的差,因此对于出差人员的感受非常直接,然而在行政这边,估计许多人如我一般,对她/他们了解甚少。与程序猿不同,她们(多数是女性)的时间被扯得稀烂,最新来的事情优先级最高。她们善良体贴,乐于帮助他人,一点点的工作失误自己会难过得不行,而来自同事的称赞则能让她们高兴好久。
这种真实的体验让我对产品与用户之间的距离有了更深刻的理解。产品还在试运营。一天行政苦笑着跑来找我:网页出错了,500 Error。我立刻几乎可以想象,她正在使用着软件,点着界面,突然之间这个恐怖的、白色的、带着几行红色粗体的错误信息页面出现在面前,对于一个没有任何编程经验的用户,是多么恐怖的体验。一种无法解释的内疚油然而生,我看着她说:对不起,理解你的感受,我马上修复。
她楞了一下,迅速说:没事,不着急。
几分钟之后修复好了。她在IM上回复说:太感谢了!
这些让我对软件的价值重新考虑。我们需要真正的使用这些软件,才能够得出真正的体验,并能够在使用不便、出现问题时真正感同身受的说:说:对不起,我理解你的痛苦。我们正在修复。
很多软件忽视这个过程。软件的设计者、制造者、销售者、客服与用户之间,通过各种部门、KPI隔离开来,然而又妄图通过各种制度来标准化服务流程。可惜,销售者不用自己的软件,客服也没用过自己的软件,制造者根本见不到用户使用的样子,又如何能够真正体会用户的感受,制造出贴心的软件,提供贴心的服务呢?
Retread of post orginally made on 06 Sep 2004
I've heard a couple of questions recently about coming up with a standard story point mechanism for multiple teams using extreme programming's planning approach. The hope is have several teams all using equivalent story points, so that three story points of effort on one team is the same as on another.
I think trying to come up with this at best of limited value, and at worst dangerous.
The estimating system of extreme programming is based on XpVelocity and YesterdaysWeather. Inherent in this is the idea that when you make estimates, the actual units you estimate aren't important - what's important is you estimate by rough comparative value and use YesterdaysWeather for calibration.
In this situation the story points act as an anchor for the feedback loop that Yesterday's Weather provides - nothing more. Baked into them are all sorts of assumptions about the nature of the team's task, the capability of the team, and whether the team are optimistic or pessimistic estimators. Once you try to come up with a standard across teams you are trying to normalize all of these factors. Trying to do this sounds very hard to me, and I'm not aware of anyone who has done this effectively. This doesn't mean its impossible, it just means it's hard.
The dangerous aspect comes from once you have a standard unit for measurement across teams, someone is inevitably going to use it to compare the performance of teams. Even if everyone swears till they are blue in the face that they won't use it for cross team measurement, there will always be the suspicion that this will happen eventually. This will cause teams to distort their measurements so that it seems that they get more story points done. My fear is that this will break the feedback loop of yesterday's weather and knock the planning process off kilter. I'm always suspicious about these things because while it would be incredibly valuable to have a way to measure productivity I think the nature of software is such that we CannotMeasureProductivity.
So to be worth trying, this has to yield some valuable benefits - but I don't see any. One reason that I've heard is to help people move onto teams and estimate more quickly. But you can't estimate on a new team until you get reasonably familiar with the problem and the current code base. As you do that you'll also get a feel for the relative sizes of story points on that team. We move people around between projects at ThoughtWorks and I've never heard anyone complain about difficulty of estimating when coming onto a new team due to differences in story points.
reposted on 10 May 2012
ThoughtWorks embraces the individuality of the people in the organization and hence the opinions expressed in the blogs may contradict each other and also may not represent the opinions of ThoughtWorks.