As any experienced coder knows, there’s some code that they are proud of and other code that is just there to get the job done. The latter is often referred to as “a hack” or “hacky code.” While it’s fair to say that coders wish all of their code could be beautiful, the reality of implementation requirements, delivery timelines, and competing demands nearly always force some amount of compromise. This leads many developers to approach projects in one of two ways:
| Conditions | Approach |
|---|---|
|
Write good code |
|
Hack |
While it’s not always this black & white, most developers are balancing these two approaches in their head and their code is an amalgam of these two approaches. In OpenMRS, we often contrast “Development” with “Implementation coding,” where the latter tends to involve more hacking to meet time constraints.
How can we leverage these two approaches? Do these approaches have to contradict each other or can we make them complimentary? This is where hacking in the right direction comes in:
The next time life intervenes and prevents you from taking the time to generate beautiful code, before you just hack something out, take a moment to consider how you could hack in the right direction. This approach takes your ultimate goal(s) into consideration, but allows for guilt-free compromises.
The fundamental requirement of hacking in the right direction is understanding where you want to be. While a journey of a thousand miles may begin with a single step, it helps to take that step toward the destination.
How does this work in real life? I’ll give you an example that came up not too long ago. Imagine that you are asked to make a patient selection widget for an application. It would be pretty natural to have a method like this:
/* Get the selected patient */
public Patient getSelection();
But what if you took 20-30 minutes to review how the patient selection widget will be used in the application and learn that we want to use it for all patient selections, including for report generation where more than one patient may be selected? Then you would probably change your method to something like this:
/* Get the selected patient(s) */
public Patient[] getSelection();
It’s a subtle change, but it could have a dramatic effect on how you write and use your code. In this example, it’s a simple change to the method signature, so worth the effort. The trick is finding these easy wins without over-designing your hack. This approach will allow you to meet tight time constraints while at least building toward a longer-term vision.
It’s not too hard and the investment is small, even when time constraints are tight. The next time you need to hack, take a moment, and try to hack in the right direction!
Playing around with cURL and learning about some handy online tools…
![]()
Visit requestb.in and you’ve instantly got a link against which you can post data and see the results.
clbin.com is a nifty tool for directing command line output straight to a short url.
If you own a Mac and haven’t discovered Alfred 2 yet, well I’m sorry. Combined with its PowerPack, it’s a formidable tool for doing things quickly on the Mac. I’ve used QuickSilver and LaunchBar. I held out for a long time as a staunch LaunchBar user, but recently made the switch to Alfred 2 and I’ve got it doing anything everything I need (and boy do I need a lot… I’m lazy and I’ll love keyboard shortcuts). The only shortcoming with Alfred is its clipboard history, which is limited to text (no images… bummer). LaunchBar does the clipboard history just right, so I map Alt+Cmd+K to LaunchBar’s clipboard history and I’m good to go. Sure, LaunchBar is an expensive tool just for clipboard history, but I already owned it and why use Alfred’s crappy clipboard history when I have access to LaunchBar? 🙂
Anyway, I thought I’d share a few of my (now ~24) Alfred 2 workflows for anyone interested:
| Arrows Easily type arrow characters (↑, ↓, ←, →) using HTML entity names: uarr, darr, larr, and rarr. |
|
| IRC Cloud Open IRC Cloud in Chrome with irc. If you’ve already got IRC Cloud in a tab, it doesn’t open it in a second tab. |
|
| OpenMRS A workflow for OpenMRS developers with a few handy shortcuts.
|
|
| Volume Controls Provides some shortcuts for controlling your volume. You thought you were lazy? I know there are dedicated keys for this, but they’re way over there. Alfred shortcuts can be habituated (once learned, you just think and it happens) and don’t require your hands to leave the “home row” of the keyboard.
|
Have you ever seen a harvest moon?
Have you ever wondered why the moon looked so big? Is it some sort of refraction of the atmosphere? Actually, it’s just an optical illusion. It’s time for the One-Eyed Pinky Trick!
Notice anything different?
That’s right, the moon is smaller. Don’t believe me? Try it. It works on the sun too (just don’t stare at the sun, ok?).
How does this work? Here’s my theory…
Poof! Optical Illusion busted thanks to the One-Eyed Pinky Trick!
I recently discovered IRCCloud and it has brought me back to IRC. I no longer have to worry about opening or maintaing a client or an IRC bot. I can check into IRC on my own terms and easily catch up on what I’ve missed (even days of history) with little effort. Perfect!
But it gets better. After a day or so playing around with IRCCloud, I hopped into the feedback channel to give some props to the IRCCloud team and ask some questions. I had noticed that I couldn’t page up/down through the history using the keyboard. Look what happened:
From feature request to deployment in under 4 hours… now that is continuous delivery! Well done, IRCCloud!!!
IRCloud is still in a beta phase. You can sign up, but it might be a while before you get an invite. If you’re looking for an invite to IRCCloud Beta, let me know and I may be able to send you one.
I wanted to check out OpenMRS trunk and a couple branches from the subversion repository within the same local copy so that I could apply a set of changes to trunk and backport it to a couple prior versions in a single commit. Typically, when you check out a directory, you get everything underneath. But OpenMRS trunk and the branches I wanted are only a few of the bazillion folders and files underneath http://svn.openmrs.org/openmrs/. Checking out all of those folders would take a long time, place an unnecessary tax on the OpenMRS repository, and end up transferring a bazillion files that I didn’t need. While it’s not obvious, there is a way to do this with Subversion. The answer is in Subversions sparse directories feature. Thank you, stackoverflow! Here’s how I checked out only a subset of folders (trunk and a couple branches) from subversion:
$ svn co --depth empty http://svn.openmrs.org/openmrs
$ svn update --set-depth infinity openmrs/trunk
$ svn update --set-depth empty openmrs/branches
$ svn update --set-depth infinity openmrs/branches/1.8.x
$ svn update --set-depth infinity openmrs/branches/1.9.x
I just ran into a handy little utility: jsonpretty.
$sudo gem install json jsonpretty
While
$curl -i http://localhost:8081/openmrs-standalone/ws/rest/v1/catalog
gives you something like this:
{"catalog":[{"name":"Cohort","operations":[{"name":"GET http://localhost:8081/openmrs/ws/rest/v1/cohort?q","description":"Fetch all non-retired that match this parameter"},{"name":"GET http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}","description":"Fetch by unique uuid"},{"name":"GET http://localhost:8081/openmrs/ws/rest/v1/cohort","description":"Fetch all non-retired"},{"name":"POST http://localhost:8081/openmrs/ws/rest/v1/cohort","description":"Create with properties in request"},{"name":"POST http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}","description":"Edit with given uuid, only modifying properties in request"},{"name":"DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}?!purge","description":"Delete this object from the database"},{"name":"DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}?purge","description":"Delete this object from the database"}],"url":"http://localhost:8081/openmrs/ws/rest/v1/cohort","representations":[{"name":"ref","properties":["uuid","display","links"]},{"name":"default","properties":["uuid","name","description","voided","memberIds","links"]},{"name":"full","properties":["uuid","name","description","memberIds","voided","auditInfo","links"]}]},{"name":"CohortMember","operations":[{"name":"GET http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members/{uuid}","description":"Fetch by unique uuid"},{"name":"GET http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members","description":"Fetch all non-retired"},{"name":"POST http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members","description":"Create with properties in request"},{"name":"POST http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members/{uuid}","description":"Edit with given uuid, only modifying properties in request"},{"name":"DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members/{uuid}?!purge","description":"Delete this object from the database"},{"name":"DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members/{uuid}?purge","description":"Delete this object from the database"}],...
adding jsonpretty:
$curl -i http://localhost:8081/openmrs-standalone/ws/rest/v1/catalog | jsonpretty
gets you something much prettier:
{
"catalog": [
{
"name": "Cohort",
"operations": [
{
"name": "GET http://localhost:8081/openmrs/ws/rest/v1/cohort?q",
"description": "Fetch all non-retired that match this parameter"
},
{
"name": "GET http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}",
"description": "Fetch by unique uuid"
},
{
"name": "GET http://localhost:8081/openmrs/ws/rest/v1/cohort",
"description": "Fetch all non-retired"
},
{
"name": "POST http://localhost:8081/openmrs/ws/rest/v1/cohort",
"description": "Create with properties in request"
},
{
"name": "POST http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}",
"description": "Edit with given uuid, only modifying properties in request"
},
{
"name": "DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}?!purge",
"description": "Delete this object from the database"
},
{
"name": "DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}?purge",
"description": "Delete this object from the database"
}
],
"url": "http://localhost:8081/openmrs/ws/rest/v1/cohort",
"representations": [
{
"name": "ref",
"properties": [
"uuid",
"display",
"links"
]
},
{
"name": "default",
"properties": [
"uuid",
"name",
"description",
"voided",
"memberIds",
"links"
]
},
{
"name": "full",
"properties": [
"uuid",
"name",
"description",
"memberIds",
"voided",
"auditInfo",
"links"
]
}
]
},
...
Nice! Thank you jsonpretty! 🙂
I wanted to list out some observations for a patient and used the following Groovy script:
def sql(s) {admin.executeSQL(s,true) }
patientIdentifier = "999-3" // a test patient, of course
sql("""
select
date(o.obs_datetime),
(select name from concept_name where concept_id=o.concept_id limit 1) as question,
case c.datatype_id
when 1 /* numeric */ then cast(o.value_numeric as char)
when 2 /* coded */ then (select min(name) from concept_name where concept_id=o.value_coded)
when 3 /* text */ then value_text
when 6 /* date */ then cast(date(o.value_datetime) as char)
when 7 /* time */ then cast(time(o.value_datetime) as char)
when 8 /* datetime */ then cast(o.value_datetime as char)
when 10 /* boolean */ then if(o.value_numeric=1,'TRUE','FALSE')
else '?'
end as answer
from
obs o
left outer join
concept c
on c.concept_id=o.concept_id
where
o.person_id = (select patient_id from patient_identifier where identifier = '$patientIdentifier' limit 1)
order by
o.obs_datetime desc
""").collect{ it.join(": ") }.join("n")
which generated output like this:
2011-02-18: PATIENT HAD SEX IN LAST 6MO: TRUE 2011-02-18: PlAN FOR METHOD OF FAMILY PLANNING, DETAILED: ? 2011-02-18: QUANTITY: 5 2011-02-18: FAMILY PLANNING METHOD PLAN: INITIATION 2011-02-18: METHOD OF FAMILY PLANNING: ECPS 2011-02-18: HIV DISCLOSURE TO ANYONE, SPECIFIC: OTHER HOUSEHOLD MEMBER 2011-02-18: REASON FOR REFUSAL - FAMILY PLANNING: TRYING TO CONCEIVE NOW 2011-02-18: PlAN FOR METHOD OF FAMILY PLANNING, DETAILED: ? 2011-02-18: METHOD OF FAMILY PLANNING: MALE CONDOMS 2011-02-18: QUANTITY: 5 2011-02-18: FAMILY PLANNING METHOD PLAN: INITIATION 2011-02-18: METHOD OF FAMILY PLANNING: BTL 2011-02-18: FREETEXT, GENERAL: POSITIVE 2011-02-18: REASON FOR REFUSAL - FAMILY PLANNING: ABSTINENCE 2011-02-18: FAMILY PLANNING: TRUE 2011-02-18: REVIEW OF MEDICAL HISTORY: ICTERUS 2011-02-18: PATIENT REPORTED PROBLEM: YES 2011-02-18: CURRENT MEDICATIONS: ALUVIA 2011-02-18: REVIEW OF MEDICAL HISTORY: DEPRESSION
Just upgraded to Firefox 4 beta 7 on my MacBook Pro and the (three finger) swipe down gesture I had grown to love (thanks to Will Henderson’s MultiClutch), suddenly became a “show tab view” gesture. This is no good! I’ve already become accustomed to swiping down to close tabs in Firefox.
A little googling, and I was able to get back my swipe-down-to-close-tab gesture and move the show tab view gesture to SHIFT + swipe down:
Now that gestures are supported on Firefox, I no longer need MultiClutch settings for Firefox.
Making a desktop shortcut icon on the Mac is pretty easy. Open up the AppleScript script editor and make a script like this:
do shell script “/Applications/Firefox.app/Contents/MacOS/firefox-bin -P Developer -no-remote &”
Then save as an application on the desktop. Done.
This example shows how to pass parameters from AppleScript. In particular, it shows how to open a 2nd Firefox window with the “Developer” profile by passing some command line parameters from AppleScript.