I've been doing interviewing and hiring at our technology startup for almost a year now. When we began hiring, I found that I was basically the only person who would do it -- partly because I was the only person who displayed any sort of natural aptitude at judging another Engineer's technical and personal abilities.
It didn't take me long to create my own system which I thought was both fair to the interviewee (whom I empathized with, having been one for so long), and gave me a decent understanding of the candidate's qualities. With a little bit of intuition, and some trial-and-error, I evolved my system.
As the people I had interviewed and hired began to join me in the interviewing process, I got to notice how other people handled going from interviewee to interviewers, and how their outlook on a candidate's performance differed from mine.
This evolutionary process has lead me to solidify the following as my Golden Laws (tm) of hiring. Most people who are experienced with interviewing will not be surprised I think, but when a new person joins our team, I find that I need to explain these rules each time anew, so I thought I'd write them down.
1. Hiring is the single most important thing a company can do because it directly affects all other things that are done thereafter.
2. Hire only people you completely trust to fulfil a given role. Then completely trust those people.
3. Your job as an interviewer is NOT to hire "good" people -- that's an undecidable problem. Your job as interviewer is to REJECT substandard people.
4. The most critical personality trait to look for in a candidate is Honesty. All other important properties flow from that font. Dishonesty in an interview is the Kiss-of-Death.
5. Every thing you need to know about an Programmer, you can learn from having them explain their own code to you.
(And lastly, the Green Interviewer's Trap: Hiring is not a popularity contest. Most interviewees are a nice people.You are not required to hire nice people just because they're nice.)
The last law is the title of this entry because its perhaps the most controversial item. Yet I hold it to be the most essential truth I have discovered about hiring Programmers. So much so, I am going to let you in to my entire technical review process, and how it is centred around item 5:
The Sample
After reviewing the candidate's resume, and mentally making note of potential strengths and weaknesses, I always request a code sample from the candidate, in the following order of preference:
1. Open Source or otherwise Publicly Available Source
Open source is the best way to possibly find out how good a programmer is. A public SCM server has all history and contribution information regarding that project. You can graph a programmer's entire history with that code base to the most minute detail.
Mailing lists have discussions of technical matters, and help show how well the candidate gets along with other people.
Open Source is usually a work of passion, and outside the corrupting influence of bad company management, so you have already factored out the naked element of talent.
2. Redacted Source code taken from a company project (without violating any applicable laws, be careful)
Proprietary (but redacted in order to comply with confidentiality laws) source code is good because it shows what the candidate produces when given a real-world problem in real-world circumstances. Particularly it shows if they tend cut corners to meet deadlines or other practical constraints.
3. School Coursework (if a semi-recent graduate)
School coursework, if not too dated, is good because it shows what the candidate can do when asked to really apply their intellect to a serious problem. It's also an opportunity for the candidate to explain how his coding style has evolved over time, and why he would make different decisions today.
4. Toy Project selected by Candidate
Candidate-selected toy project is good because it shows what interest the candidate has if someone is not over their shoulder telling them what to do.
Do they choose something just difficult enough to meet your requirements? Do they choose something way too hard to complete in a reasonable amount of time? Do they report enjoying the exercise?
5. Toy Project selected by Me
One reason why I neglect this option to the last is that one must be respectful that candidates often are busy people, and they can and will turn down the interview because they feel they have better things to do than jump through your recruiting hoops.
6. Source written by Me
This is fun for showing off your work, and is realistic in the sense that programmers are routinely asked to do exactly that, however its rarely under such pressure situations. It's too easy to have a flustered programmer get thrown off by some minor detail, and thus you end up with incomplete reading of their true ability.
Either way, it is absolutely critical that the code is either hand-written by the candidate (or in cases where external code sources were used, clearly attributed as belonging to someone else), because we are going to ask them to explain its working to us. Clear attribution is critical because it is an unalterable part of our standard of honesty.
The Interview Prep
Once you have a sample of their code, you can of course review the code before the Interview to look at issues of Style, Correctness, Performance, Good Design. This is important. However the real purpose of reviewing the code is to generate questions for the candidate once he arrives to the interview. Ask yourself, "why did this person choose to make these decisions when writing the code?"
The Interview
When the Candidate arrives, I open the interview with a stupid joke or sarcasm. Partly because I often am as nervous as the candidate, and I want to break the ice. I also tell him that there are no "Right" answers in this interview, and no trick questions. It is important for the dignity of both parties to keep the interview cooperative, and to make it understood that you are both on the same side -- after you hire the candidate, you certainly will be counting on their cooperation and good will then!
Next I have a short list of simple, no-nonsense, general questions that I ask every candidate. For example "We are in the technology field, and as we both know, technology changes at a staggering rate. What methods do you use to keep on top of that change? Please be specific."
Next I have the list of questions generated from their sample code with me, and I move across the table to sit beside to the candidate with my laptop. I open their source code opened in a text editor (their preferred on if possible).
I ask the candidate to pretend that I am a Junior Engineer, and he must explain his code to me. As a curious person, I will stop them at various points to clarify the concerns I compiled during the Interview Prep, and put to rest any questions that happen to come into my mind as they are speaking.
What an observer can gather from this simple exercise is essentially everything you'll ever need to know about that person as a Programmer.
- A person who remembers even very old code is a person that was paying close attention when they wrote it, and understood the nuance of every line.
People who have trouble recalling what they wrote either didn't write it, or never understood it clearly when the wrote it.
- A person who speaks confidently and at a decent pace is a person at ease with their code and the decisions that code embodies. Even if the code is bad, a good programmer can justify their compromises in a rational manner.
People who speak in broad generalities, ramble incoherently, or carry off on tangents are more than just disorganized. They are trying to distract you from the fact that they don't know what they are talking about by burrying you in an avalache of verbage.
- A person who identifies their own mistakes and errors is more than just aware of their own mistakes, they are showing an honesty and desire to learn and improve. Similarly, people admit their don't know or understand some facet of the system.
A person who steadfastly insists on issues where you know him to be mistaken are not capable working with others, and should be rejected immediately.
- A person that can turn a complicated algorithm or technical trick into something easily understood by any simple engineer has not just a gift for teaching, but likely a deep and intuitive understanding of domain.
People who cannot relate technical matter without speaking entirely above your level may be brilliant, but could prove problematic if they have to work with others.
That said, even simple body language can tell you a lot about a candidate. One of the most dependable Engineers I've ever worked with failed horribly in trying to understand some source code I had written (possibly because I hadn't written it nearly as well as I thought I had!). Every time I asked him what a certain peice of code was doing, he politely answered "I don't know". What impressed me was the way in which he studied my code so closely, determined to fathom out my design through force of will. To this day his tenacity to reason through a problem completely is the hallmark of his good work with our company.
The wind-down
If I have come to a negative conclusion about the candidate, and decided not to continue, I ask the if he has any questions, then thank the person for their time, and ask them to leave.
If I have finished my walk-through on a positive note, I wrap the interview up with a brief explanation of my company, then invite the candidate to ask his own questions of us in a reversal of the interview process.
A complete and incisive set of questions for me to answer is a sign of a good candidate.
If I conclude that a person is technically competent, the next round of interviews include various members of our team, and are mostly about making sure that personalities match well, and that company culture is maintained.
If any one interviewer registers a "refuse" vote, then the candidate is refused.
Thursday, June 19, 2008
Every thing you need to know about a Programmer, you can learn from having them explain their own code to you.
Thursday, May 29, 2008
Coming Home
I should have posted this a very long time ago, but I've been rather busy with work and life.
It seems that since I got back from NY in April I've been running around doing something, or just being plain run down. I had my friend Anand in town, and we had a good time about Tokyo. Now Deifante has come, and we'll be off to the Virtual World Conference and Expo here in Tokyo. The reason D. is in town is because we are kicking off a new project that I am ostensibly leading, and D. is contracting to work with us on it.
However, I am now planning to pack up my family and head back to Canada for the first time in over 2 years. It will be L.'s first trip on an airplane, and my family's first time seeing L. in person.
If anyone I know in Calgary would like to meet me or M. or L., please drop me an email, and I will do my best to arrange it. :)
Tuesday, May 6, 2008
If I had a wedding, and I had tried this line at it, I think I would have lost exactly everyone
[Reads]
You that choose not by the view,
Chance as fair and choose as true!
Since this fortune falls to you,
Be content and seek no new,
If you be well pleased with this
And hold your fortune for your bliss,
Turn you where your lady is
And claim her with a loving kiss.
A gentle scroll. Fair lady, by your leave;
I come by note, to give and to receive.
Like one of two contending in a prize,
That thinks he hath done well in people's eyes,
Hearing applause and universal shout,
Giddy in spirit, still gazing in a doubt
Whether these pearls of praise be his or no;
So, thrice fair lady, stand I, even so;
As doubtful whether what I see be true,
Until confirm'd, sign'd, ratified by you.
-- BASSANIO, SCENE II. Belmont. A room in PORTIA'S house.
Thursday, May 1, 2008
So this is what my Degree comes to...
After many years of study, it comes down to solving logarithms in your head while you're trying to get to sleep.
How many rounds of Russian Roulette does it take before you have 50-50 or worse odds of surviving?
If we assume there is 1 bullet in a 6 chambered revolver, you have a 1/5 chance of having your wig pushed back, and 5/6 chance of taking another breath. The problem is that every time you play (and survive to play again), you're taking another trial with 5/6 odds, which gives you (5/6)^n probability of surviving after n rounds.
Problem is that when you multiply a number less than 1, you always get something smaller than what you started with. That means one day, you will bite the bullet. The question is, after how many tries?
We wish to solve the following for n:
(5/6)^n = 1/2
=> lg (5/6)^n = lg (1/2)
=> n * lg (5/6) = lg (1/2)
=> n * (lg(5) - lg(6)) = lg(1) - lg(2)
=> n * (lg(5) - lg(2) - lg(3)) = -1
Now here we reason that since lg() is a continuous, monotonically increasing function, by the intermediate value theorem:
2 < 3 < 4 < 5
=> lg(2) < lg(3) < lg(4) < lg(5)
= 1 < lg(3) < 2 < lg(5)
That gives us a tight enough bound for lg(3) ~ 1.5, considering the precision we need only to the nearest whole number, but the bound on lg(5) is pretty loose, so we'll need to do better.
We know that we can approximate an analytic function, as lg() certainly is, about a point "a" using its derivative:
lg(x) ~ lg(a) + lg'(x) * (x-a)
We also know that
lg'(x) = 1/(x * ln2)
where e ~ 2.7, so we take ln2 ~ 1.
Together this gives:
lg(5)
~ lg(4) + 1/5 * (5-4)
= 2 + 1.5 = 2.2
Going back to the original equation:
n * (lg(5) - lg(2) - lg(3)) = -1
=> n * (2.2 - 1 - 1.5) = -1
=> n = 1 / 0.3 = 3.3
Lets verify that we haven't wandered completely off-base by checking our calculation:
(5/6)^3 = 125 / 216 > 125 / 250 = 1/2
=> (5/6)^3 > 1/2
(5/6)^4 = (125 * 5) / (216 * 6) = 625 / 1296 < 1250 = 1/2
=> (5/6)^4 < 1/2
So there you have it: 3 rounds of everyone's favorite pastime gives you better than 50-50 odds, and 4 rounds give you worse than 50-50 odds. Running it through the calculator the next morning, you have a 57% chance of surviving 3 rounds, and 48% after 4.
Now, play a game of Russian Roulette and have a blast.
Monday, April 21, 2008
Asterisk Realtime Architecture
Like any machine tinkered with heavily over time, Asterisk has a lot of exposed configuration points in a lot of places, and it can be hard to know how or why what you want to do isn't working because you neglected to set some variable that became necessary since the last time the module was documented.
I've fallen into this situation for the past couple days, and having finally found the right thing to tweak, let me write down how to use ODBC to access Asterisk's configuration files, including the dial plan, from a real-time database.
First thing: go read the official book by O'Reilly, and read the chapter on ARA.
Once you have downloaded Asterisk, as we did here, run ./configure, then make menuselect and ensure res_odbc, res_config_odbc, and all *_realtime modules are set
to be built. If they have a 'XXX' beside their name, it means that you need to install some dependencies. Search your package installer for "ODBC" and a database that supplies an ODBC driver, such as PostgreSQL or MySQL. You may also need the "-devel" version of those packages.
To make the tutorial complete, we'll pick a DB for the examples, so we choose PostgreSQL.
1. Setting Up the Database
As the DB's (not the system's) root user create a new role and database for Asterisk to use:
createuser -SRdP asterisk-role
createdb --owner=asterisk-role asterisk-db
Then restart the database.
2. Setting Up ODBC
Ensure that /etc/odbcinst.ini exists and contains something like the following:
[PostgreSQL]
Description = ODBC for PostgreSQL
Driver = /usr/lib/libodbcpsql.so
Setup = /usr/lib/libodbcpsqlS.so
FileUsage = 1
Ensure that /etc/odbcinst.ini exists and contains something like the following:
[asterisk-postgres-connector]
Description = PostgreSQL connection to 'asterisk' database
Driver = PostgreSQL
Database = asterisk-db
Servername = localhost
UserName = asterisk-role
Password = 123456
Then verify that the connection works using the following command:
echo "select 1" | isql -v asterisk-postgres-connector
3. Setting Up a Basic Asterisk Configuration
extconfig.conf
[settings]
sipusers => odbc,asterisk-postgres-connector,sip_table
sippeers => odbc,asterisk-postgres-connector,sip_table
extensions => odbc,asterisk-postgres-connector,extensions_table
extensions.conf
[general]
[default-sip]
switch => Realtime/@
modules.conf
[modules]
autoload=yes
preload => res_odbc.so
preload => res_config_odbc.so
res_odbc.conf
[ENV]
[asterisk-postgres-connector]
enabled => yes
dsn => asterisk-postgres-connector
username => asterisk-role
password => 123456
pre-connect => yes
sip.conf
[general]
4. Setting up Dialplan and Sip User/Peer Tables
This is perhaps the most mysterious point. The ARA requires a number of columns to be set within the table, and if they aren't it will silently fail. The SIP table in particular. Nearest I can tell, the only source of documentation is here.
Run the following SQL code as the asterisk-role:
CREATE TABLE extensions_table
(
"id" serial,
"context" varchar(20) NOT NULL default '',
"exten" varchar(36) NOT NULL default '',
"priority" smallint NOT NULL default '0',
"app" varchar(20) NOT NULL default '',
"appdata" varchar(128) NOT NULL default '',
PRIMARY KEY (id)
) WITHOUT OIDS;
CREATE TABLE sip_table
(
"id" serial,
"name" varchar(80) NOT NULL default '',
"host" varchar(31) NOT NULL default 'dynamic',
"nat" varchar(5) NOT NULL default 'route',
"type" varchar(6) check (type in ('user','peer','friend')) NOT NULL default 'friend',
"accountcode" varchar(20) default NULL,
"amaflags" varchar(13) default NULL,
"callgroup" varchar(10) default NULL,
"callerid" varchar(80) default NULL,
"call-limit" int NOT NULL default '0',
"cancallforward" char(3) default 'no',
"canreinvite" char(3) default 'no',
"context" varchar(80) default 'default-sip',
"defaultip" varchar(15) default NULL,
"dtmfmode" varchar(7) default NULL,
"fromuser" varchar(80) default NULL,
"fromdomain" varchar(80) default NULL,
"insecure" varchar(4) default NULL,
"language" char(2) default NULL,
"mailbox" varchar(50) default NULL,
"md5secret" varchar(80) default NULL,
"deny" varchar(95) default NULL,
"permit" varchar(95) default NULL,
"mask" varchar(95) default NULL,
"musiconhold" varchar(100) default NULL,
"pickupgroup" varchar(10) default NULL,
"qualify" char(3) default NULL,
"regexten" varchar(80) default NULL,
"restrictcid" char(3) default NULL,
"rtptimeout" char(3) default NULL,
"rtpholdtimeout" char(3) default NULL,
"secret" varchar(80) default NULL,
"setvar" varchar(100) default NULL,
"disallow" varchar(100) default '',
"allow" varchar(100) default '',
"fullcontact" varchar(80) NOT NULL default '',
"ipaddr" varchar(15) NOT NULL default '',
"port" smallint NOT NULL default '0',
"regserver" varchar(100) default NULL,
"regseconds" int NOT NULL default '0',
"username" varchar(80) NOT NULL default '',
"delay" int NOT NULL default '0',
"sortorder" int NOT NULL default '1',
PRIMARY KEY (id),
UNIQUE (name)
) WITHOUT OIDS;
GRANT ALL ON sip_table TO asterisk;
GRANT ALL ON extensions_table TO asterisk;
INSERT INTO extensions_table (context, exten, priority, app, appdata) VALUES ('default-sip', 'sip-extn', 1, 'Answer', '');
INSERT INTO extensions_table (context, exten, priority, app, appdata) VALUES ('default-sip', 'sip-extn', 2, 'Wait', '2');
INSERT INTO extensions_table (context, exten, priority, app, appdata) VALUES ('default-sip', 'sip-extn', 3, 'Playback', 'hello-world');
INSERT INTO extensions_table (context, exten, priority, app, appdata) VALUES ('default-sip', 'sip-extn', 4, 'Wait', '2');
INSERT INTO extensions_table (context, exten, priority, app, appdata) VALUES ('default-sip', 'sip-extn', 5, 'Hangup', '');
INSERT INTO sip_table (name, context, host, type) VALUES ('sip-user', 'default-sip', 'dynamic', 'friend');
5. Test the setup
Run Asterisk on the console, and verify the ODBC connection is working by issuing the command odbc show.
Don't bother trying to run dialplan show or similar, as this will not display what we inserted in the previous step. Instead we need to make a call to test: in your soft-phone, dial sip:sip-extn@asterisk.example.com, and you should hear a voice say "hello world".
Thursday, April 17, 2008
Working with Asterisk
Recently I have been working on some VoIP based solutions using Asterisk.
I've found it a bit hard to work on, partly because Asterisk appears to suffer from some ad-hoc growth over time, and not only is the design somewhat dated, but more importantly the documentation left around the web appears to be tied to past versions such that working with the newest versions can leave you scratching you head why what one guy did, and what you're doing don't seem to match up. Even when you manage to get the example working you're never quite sure which flag you twiddled happened to do the trick.
(Aside: there seems to be a lot of consulting business around Asterisk. Partly this is due to the inherently Enterprise-related nature of a PBX. However I have to wonder if these consultants don't have a vested interest in keeping Asterisk opaque. Certainly they'd have more clients than if the community spent time making it easier to use.)
First thing one needs to do when learning a system is to make some small examples. The easiest way to test your examples, lacking the sort of hardware one might have if they owned a real Enterprise phone system, is to use software phones. The most common kind of software phone is one that uses SIP to set up an audio stream (over RTP) for voice communication.
To obtain a working Asterisk server, download either the source, or a pre-compiled binary from your favorite location. If you're working a lot with Asterisk it makes sense to build the source yourself, since it's rather easy to build.
After you have completed the install, your /etc/asterisk/ directory should be full of a wild assortment of configuration files, each over-teeming with tweak-worth options. However just to set up a simple call, we don't need such complexity. Copy those files to a safe place, and consider the minimal files below:
modules.conf
As far as I can tell, this is all you'll ever need from modules.conf, under any situation.
[modules]
autoload=yes
This means that as Asterisk looks for .conf files (which we have mostly removed), and processes its internal and external dependencies, it will automatically load the binary files in /usr/lib/asterisk/modules.
sip.conf
This file controls the SIP input "channel", from which any session created from sip:a-sip-user@example.com to sip:somewhere@asterisk.example.com, must pass first on its way through an Asterisk system. The call from a-sip-user is assigned a "context" which is where in the "dialplan" the call should be routed. ("user" means that the call is in-bound only, and "dynamic" means the server should look up the user's IP address dynamically)
[a-sip-user]
context=my-sip-context
type=user
host=dynamic
This means that anyone who call into Asterisk, and declares themselves to be "a-sip-user" via their SIP URI (such as sip:a-sip-user@somewhere.example.com), is given a dial plan context of "my-sip-context".
extensions.conf
This is the "dial plan", the place where the routing logic of a PBX is stored. It consists of a number of named contexts, under which a series of sequential functions are called. It will appear odd to a modern programmer, but if one considers the evolution of old-fashioned hardware based PBXs, it makes more sense.
Basically there is first an "extension", which is related to the old extension numbers you would have to dial over an old-fashioned system. However with software based PBXs, those extensions can now easily be human-readable strings.
A call is brought in to the dial plan from a channel. The only channel we are considering now is a SIP-based VoIP call. That channel attached a context to the call, and starting from the named context, the dial plan attempts to match an extension pattern to the one specified in a call.
In the case of a SIP call, where the URI is sip:my-destination@asterisk.example.com, the extension is considered to be "my-destination".
[my-sip-context]
exten => sip-extn,1,Answer()
exten => sip-extn,n,Wait(2)
exten => sip-extn,n,Playback(hello-world)
exten => sip-extn,n,Wait(2)
exten => sip-extn,n,Hangup()
This means that when dropped in the "my-sip-context" context, Asterisk will attempt to match the extension against "sip-extn". The first action to be taken is to answer the call. The steps with priority marked "n" proceed sequentially from each other.
In the example above, the soft-phone must dial the following URI: sip:sip-extn@asterisk.example.com as the user designated sip:a-sip-user@somewhere.example.com. Upon doing so, they should hear a female voice saying "hello world!"
If you are using ekiga as your soft-phone, go to Edit->Accounts, and then add a new one. The name can be any thing you like, however the registrar must be asterisk.example.com (that is the name or IP address of the machine running Asterisk), and the user must be a-sip-user. The password is empty because we have not required password protection in sip.conf.
Click OK and confirm that the Status indicates that it is registered with the server. If registration fails, check that the server's firewall is not blocking port 5060, and that you are not using the soft-phone from the same machine that Asterisk is running on since port numbers will conflict.
In the top-most address bar, enter the URI sip:sip-extn@asterisk.example.com and click enter. You should now hear the words "hello world!"
The version of Asterisk I used was 1.6.
Sunday, April 13, 2008
Thursday, April 10, 2008
How do you use your shell?
[ryanm@3Di0021d ~]$ history | awk '{a[$2]++ } END{for(i in a){print a[i] " " i}}'|sort -rn|head
177 make
144 ll
96 git
77 cd
59 grep
59 gvim
40 cp
38 git-svn
32 ./secondlife
30 svn







