Sunday, 28 June 2015

Cython and regular expressions

Recap

I'm returning yet again to the long suffering text matching example from a couple of months back. The goal is/was to take a text document, a set of patterns, and see which of these patterns could be found. We went through a variety of techniques: Python micro-optimisations, regexes, C++ extensions, Twisted, reactors and improved string matching algorithms. My intent this time around is to try improvements brought out by Cython.

In case you're unfamiliar with Cython, I'll do a two sentences' introduction: its aim is to preserve the ease of development inherent to Python while extending it with static type definitions and easy C/C++ bindings. Considering that dynamic typing comes at a great cost, it's perfectly suited for optimising hotspots in Python code and striking a balance between productivity and performance.

As before, the end result turned out to be surprising (at least to me), but what matters is the journey. Let's embark on it.

Firstly, I'll shake the dust off the pure Python example:
from twisted.internet import reactor, defer, threads
import sys, re

def stopReactor(ignore_res):
   reactor.stop()

def printResults(filename, matchingPatterns):
   print ': '.join([filename, ','.join(matchingPatterns)])

def scanFile(filename, pattern_regex):
   pageContent = open(filename).read()
   matchingPatterns = set()
   for matchObj in pattern_regex.finditer(pageContent):
      matchingPatterns.add(matchObj.group(0))

   printResults(filename, matchingPatterns)

def parallelScan(filenames, patterns):
   patterns.sort(key = lambda x: len(x), reverse = True)
   pattern_regex = re.compile('|'.join(patterns))

   deferreds = []
   for filename in filenames:
      d = threads.deferToThread(scanFile, filename, pattern_regex)
      deferreds.append(d)

   defer.DeferredList(deferreds).addCallback(stopReactor)

if __name__ == "__main__":
   with open(sys.argv[1]) as filenamesListFile:
      filenames = filenamesListFile.read().split()
   with open(sys.argv[2]) as patternsFile:
      patterns = patternsFile.read().split()

   parallelScan(filenames, patterns)
   reactor.run()

This was just a copy/paste to save you the bother of clicking through to an older post. I still preserved the reactor and multi-threading, but they won't be playing a major role today.

We know that it's scanFile where we spend most of the time, and this is the hotspot that needs optimisation treatment with Cython. Since the matching is done via regexes, we cannot in good faith claim that we're optimising without also switching their library; here, I'm going to use regex.h.

Writing Cython code is not a common skill, and we're not dealing with Hello, World here, so as the narrator, I have two choices: jump to the end result, and go for an autopsy, or build it piece by piece. Taking the mantra that it's the journey that matters, I'll go for the second option.

Cython

Going for the easy bit, here's the updated scanFile function:
def scanFile(filename, pattern_regex):
   pageContent = open(filename).read()
   matchingPatterns = contentMatchPatternCython.matchPatterns(pageContent, pattern_regex)
   printResults(filename, matchingPatterns)

Nothing glamorous here, we just defer matching to the Cython extension that will be built.
Let's tick the build checkbox too:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

setup(name='contentMatchPatternCython',
      cmdclass={'build_ext': build_ext},
      ext_modules = [Extension('contentMatchPatternCython', 
                     sources = ['contentMatchPatternCython.pyx'])])

Still nothing exciting, as this is just a small bootstrapping script that will build the Cython extension.

Time to take a deep breath, and look at how we write the actual algorithm.
This is the skeleton:

def matchPatterns(bytes pageContent, bytes regex):
   cdef set matchingPatterns = set()
   return matchingPatterns
Baby steps here. We just return an empty set to satisfy the function signature, however it already differs from vanilla Python code by defining types for pageContent, regex and matchingPatterns.
Why? We'd like to use Cython's memory and performance optimisations by pre-defining the variable types which will get us optimised variable storage and access. In this specific case, there is no requirement to support unicode, hence the bytes type definition.

Now, let's import regex.h functions:
cdef extern from "regex.h" nogil:
    ctypedef struct regmatch_t:
       int rm_so
       int rm_eo
    ctypedef struct regex_t:
       pass
    int REG_EXTENDED
    int regcomp(regex_t* preg, const char* regex, int cflags)
    int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags)
    void regfree(regex_t* preg) 
Note that we only import what we need later on, and that we tell Cython to release the GIL when executing the imported functions via the magic nogil keyword.

Ok, so we have the interface and the required C library function imported into PCython. All that's left is the algorithm, and tying it all together:
cdef extern from "regex.h" nogil:
    ctypedef struct regmatch_t:
       int rm_so
       int rm_eo
    ctypedef struct regex_t:
       pass
    int REG_EXTENDED
    int regcomp(regex_t* preg, const char* regex, int cflags)
    int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags)
    void regfree(regex_t* preg) 

def matchPatterns(bytes pageContent, bytes regex):
   cdef set matchingPatterns = set()
   cdef regex_t regex_obj
   cdef regmatch_t regmatch_obj[1]
   cdef int regex_res = 0
   cdef int current_str_pos = 0
   
   regcomp(&regex_obj, regex, REG_EXTENDED)
   regex_res = regexec(&regex_obj, pageContent[current_str_pos:], 1, regmatch_obj, 0)
   while regex_res == 0:
      matchingPatterns.add(pageContent[current_str_pos + regmatch_obj[0].rm_so: current_str_pos + regmatch_obj[0].rm_eo])
      current_str_pos += regmatch_obj[0].rm_eo
      regex_res = regexec(&regex_obj, pageContent[current_str_pos:], 1, regmatch_obj, 0)

   regfree(&regex_obj)
   return matchingPatterns

The interesting stuff happens at lines 19-24 where the usual compilation and regex execution takes place. The code is not one-to-one to pure Python since the underlying C library does not support the notion of generators, and we have to do string slicing (which is cousin once removed to pointer arithmetic).

Cython is relatively new to me as well, and even though I've written the code above, it caused a certain degree of cognitive dissonance: a bit akin to someone seeing platypus for the first time.


The code looks like C, but semicolons are nowhere to be found, while there are Python indentations and weird brackets around strings. If you've been developing in both languages for a while, seeing them unified in matrimony within a single function is a new experience. 
Anyhow, time to cut the nostalgia, stop coding and start measuring! At this point, my intent was to try this on a single <file, pattern> pair, and move on to doing further Cython tweaks to ensure we can release the GIL on the entire regex matching loop. Reality, however, turned to be different...

Performance

From here, and until the end of the post, the inputs are fixed: we use a single haystack (17KB source of google.html) and a fixed set of needles (first 2000 tokens of /usr/share/dict/words).

Making sure to run a few iterations and take the fastest one, here's the timing using the Python-only example:

$ time python pythonOnly.py shortFilenamesList.txt shortWords.txt

real    0m0.868s
user    0m0.546s
sys     0m0.312s

All right, so how does Cython compare? Drum roll please...

$ time python pythonWithCython.py shortFilenamesList.txt shortWords.txt

real    0m2.396s
user    0m2.012s
sys     0m0.374s

Ouch - 2.7 times slower! Of course, such a difference has to be explained. In my mind, there are two possible sources: different regex library or Cython itself. The former is far more likely, but we should never make fast assumptions with performance tuning. Hence, the natural next step was to write the same in C. 
I'll omit the C code for brevity, as it mirrors the Cython code with the semicolons and pointer arithmetics thrown in. Let's jump to the result:

$ time ./a.exe

real    0m1.547s
user    0m1.513s
sys     0m0.031s

No joy - it's still way slower than Python. Time for

Conclusions

So, we know by now that it's the difference in the regex library that matters; no low-level optimisations in Cython or C can overcome that. In hindsight, there was little basis to suppose that Cython will matter on this specific task, as Python's re implementation is also native C.

However, this was not time wasted, and here's why:

  1. This is yet another tangible example of why blanket statements such as Language X is slow, and Y is fast are untrue. In this case, Python came up faster; in another it may well be slower. 
  2. Thanks to user @nhahtdh on StackOverflow, I gained insight into the difference in performance. regex.h implements a backtracking engine, and does not optimise non-backtracking regexes, which is almost certainly something that Python re does.
  3. Good Cython practice. There is a reasonable template in place to import C libraries into Cython, link with it, build etc. (There are of course plenty of other resources that show the same, but it's always helpful to do and gradually demonstrate it by yourself)

What next?

Coming back to the task at hand: can we truly say that Python's re engine is simply better? My answer is a definite 'no' since engines shine on different patterns. Ours has been a long sequence of OR expressions, and it is just one possibility among many.

So, there are many options to take it further: try other matching patterns, compare with C++ boost::regex, or go for examples that are less dependent on supporting libraries. While Cython has not been of much help with optimising our venerable string matching example, it yet has a future in this blog.

Monday, 15 June 2015

Interviews: what not to say or do

My previous couple of posts on hiring were for interviewer's sake: what to ask, what to tell, and in general, how to land that perfect candidate.

However, what about the interviewees? They are the ones that withstand a barrage of questions, and have far more at stake: shouldn't we give them a bit of advice too?

My recent experience runs towards the cushy part, i.e. the interviewer, but after talking to dozens of potential colleagues, I've acquired a mini-collection of behaviours that either put me off a particular candidate or vice versa. This is precisely the collection I'm going to share below.

As for the order: a while back I've been taught that in each monologue it's good to cover the negatives first and finish on positives. So, let's start with


What not to do on interviews



Don't go on a tangent. There's a great British comedy talk show called QI. In that show, one gains points by bringing up quite interesting - hence the name - facts that might or might not be related to the question asked (hint: it's usually the latter).

Well, job interview is exactly the opposite of that. Talking about B while being asked about A is a bad idea: and in case you're unsure why, I'll elaborate. Firstly, it gives me, the interviewer, a clear indication that the guy on the other end knows nothing about A. Secondly, it leads me to a deduction that on this subject they might only know about B. Thirdly, it points to a potential communication problem.
It may well sound trite and obvious, but this is by far the most frequent violation I've encountered.

A while back, I hired senior C++ developers, and one of the more advanced questions was:
When would you not define a virtual destructor? 
I took great pains to underline the word not when asking the question. Nevertheless, many people chose to explain when one would have defined a virtual d'tor, which is of course a far simpler question. A large subset answered the same inverted question again after I gave them another chance.
Needless to say, they would have been in a far better situation by just admitting ignorance.




Don't guess. Speaking of admitting ignorance - guessing an answer is never the path to fame and glory. If you honestly say that you're unfamiliar with the answer, then it perhaps highlights a technical gap, but not a personal one. Guessing at an interview, however, shows that the candidate might do the same when working with others and writing production code, and let's face it - none of us are omniscient.

One might object that you have a chance of guessing correctly, and thus avoid the double whammy. Well, unless we have a software developer and an actor all rolled into a single package, the lack of confidence in the answer will be still audible and noticeable. Note that there are shades of grey here, and it's perfectly valid to reason about the possible answer, e.g. "I'm not sure how database X implements durability, but based on database Y, I'd guess they use a temporary log buffer".

Again, it happened to me a number of times. On one interview, I've asked about the difference between HTTP/1.0 and 1.1, and after a short pause got a seemingly confident and completely incorrect answer about the latter introducing load balancing. In my mind, the interview ended right there, although of course we ran it to completion.

In short, "Don't know" is not a rude or dirty phrase. It is much better than giving the right answer to the wrong question or guessing.



Don't project over confidence. Continuing the previous thought: it's crucial to have a realistic assessment of your skills.

Picture yourself two candidates: both being able to write basic Python code, but not going far into intermediate concepts such as generators, property methods or mixin classes. One claims his Python skills as moderate, while the other puts emphasises Python experience on the CV, and declares full mastery. Which of the candidates would you progress with?

This exact situation happened to me, and obviously the second candidate got filtered out early on. The frustrating thing here is that his skill and salary expectations were good enough for the role, which was fairly junior. He simply overestimated himself, and that means that he would have been doing the same at work, and that is a recipe for interpersonal issues.

Basically, try and grade yourself ahead of the interview on each of the keywords in the CV. Go to sites such as StackOverflow and try answering questions on your favourite keywords. Does your self assessment stack up?

Don't be emotional. This is a very big no-no. It's great to express a gamut of emotions when auditioning for a role. It's also fine to be expressive during important keynotes and motivation speeches. Not so good on job interviews.



From my point of view: if a person is unbalanced even at such an artificial and special setting as an interview, what will happen when they starting working with us? Would they be a living incarnation of jack-in-the-box?
Of course, it might be that they are simply nervous, and in day-to-day interactions, they'll be an oasis of calm and reliability. They just might. But, this interview is all I have to go by, and employing a person erroneously is a costly and emotionally charged mistake.

Don't speed up. It's not the most common fallacy, but it did happen to me on a number of occasions. The interviewee treats the end of my question as a starter gun in a 100 meter dash and proceeds to firing out sentences at a serious rate of knots per minute.
I can (sort of) follow the thought pattern here: "I'm not quite sure what he is looking for, so let's do a scatter shot and see if one of the answers hits the target".




Unfortunately, the main result achieved by this method is mild headache at the other end of the wire (or room). Also, even if one of the answers is correct, it's the wrong ones that are going to matter.

However, let's switch subjects a little bit. Apart from knowing the answers to the technical questions and avoiding the pitfalls above, 

What to do before interviews?


Read up on the company and its products. It always makes a good impression when the candidate knows more than just the title page of the website. It does not take long to Google the company, and become aware about their business model and latest happenings. It also kills two birds with one stone: shows the interviewer that you are a diligent person, and helps with figuring out whether it's the right place for you.




Again, must sound very obvious, and again, not many people do this - especially software engineers. I guess sometimes the mindset is: "I'm going to develop code and get paid, their business model is not my business."
There are more than enough reasons why this is not the right thought pattern, but nevertheless - even the first bird, i.e. showing diligence, should be convincing enough.

At one call, I had a candidate succinctly describe to me all of our recent developments, with highlights from the latest Gartner report thrown in. It gave him a +50 karma points boost for the rest of the chat.

Read up on the interviewer. Fifteen years ago, average online presence was minimal. Unless you were lucky enough to be quizzed by a public figure, all you had to go by ahead of time was the interviewer name.
Today, this excuse does not hold any longer. We've got LinkedIn, blogs, forums - plenty of input to figure out what your counterpart knows and cares about. For example, if you're interviewing with me (yes, we have roles open!) - finding this blog might help.
Moreover, if that person is also your hiring manager, then you get a chance to understand compatibility and yet again, show personal diligence.

Get good questions ready. Don't think that the interview is finished whenever the technical questions end and you get the virtual or physical microphone. It is still going on, and the questions you ask cast a shadow (or light of glory) on you as a person.

Here, I had the whole range: from silence, to vocalised "10 questions to ask on interview" articles.
Silence is practically the worst option, since it shows detachment and lack of care. Asking stock questions, such as "What do you like best about working in your company?" does not do any harm, but does not do much good either. Asking stock questions gets you stock answers, and does not show personal preparation. It is a bit like the worn off "Name-your-three-best-worst-qualities" question that some employers still stubbornly hang on to.

As with the other bulletpoints, it's best to ask questions pertinent to the company, role and what you heard so far. For example,
You do your development in C++; do you go for the latest standards, and how extensively do you use external libraries?
You mentioned that this role has a customer element to it: can you describe typical customer interactions I'm likely to have?
How often do you release software? Can you describe a typical release cycle? 
As you are based on third-part IaaS systems, is there any special testing process that you follow and how do you get development environment similar to the target deployment?
Can you describe the technical career progression in your company?
These questions show that you've been attentive, wish to understand the position in depth, and that you have previous experience with similar processes.

Be ready to back up your CV. This is of course the biggie. Before an important interview, try re-reading your CV, and see if you have any stale skills that need a refresher. Obviously, it does not mean that you stand a chance of getting away with techniques you've never used, and inserted in the resume to gain attention. But, if you genuinely used a specific technology for a few years in the past, then brushing up won't do any harm, and may avoid the embarrassment of stalling up on basic questions. 




I was once interviewing a person who was mostly brought in due to his networking stack knowledge: SSL, HTTP, TCP/IP, DNS - he had them all. The most frustrating thing was not even that he could not answer entry level questions on most of them. It was that it was evident that he knew them at some point, since snippets of right terms were popping up while he was searching for the answers. However, there were also plenty of holes in other topics, and hiring based on a hunch is simply too much a risk: including for the candidate who had active employment at the time. We had to pass, while I'm sure (or hope) that if he did a half day refresher ahead of our meeting, the decision could have been different.

Wrapping up

If you followed me all the way here, you're probably regretting the 240 seconds wasted. So much obvious advice, which is already spread upon countless blogs and articles.
If that's the case, you are correct - few people will find genuinely new information here. The hardest part about changing behaviour is not reading about it, but doing it, and my task at hand was giving a few examples and sharing personal experience to stress why it's so important. 

Landing the right job is a crucial moment in our lives. It's disappointing, yet understandable, not to get it because we simply lack the skills, experience, or even if we had encountered interviewers on a bad day.
But, losing an opportunity due to not presenting yourself in the best light possible is more than disappointing; it means that we lost a chance to invest a few hours to make the next few years better.

Monday, 8 June 2015

Hiring: QA screening

This post is a small bonus chapter on top of the phone screening narrative.

So far, I've concentrated on pre-filtering developers, and mentioned test specialists very (too) briefly.

Let's take QA then, and ask again

What qualities are we looking for?

They are not that different from developers, with only coding making way for testing:


  • Can they find bugs?
  • Is there a large discrepancy between what they think they know and what they actually know? 
  • Can we work together, i.e. is there a communication problem?

And going a little bit beyond the land of the utterly obvious, and zooming in upon bug finding:
  • Is there relevant industry background (e.g. hardware appliances, or financial systems - depending on what we do)?
  • Do they understand system interactions?
  • How about intuition and ability to poke holes in software (including corner cases)?
The last one is the absolute must, so let's expand a bit:


Poking holes in software

Like with developers, here I tend to pose a simple scenario, and see how far we get. For the aesthetic sense of symmetry, I can ask to provide test cases for the same function that was brought up in the developer coding interview.

Now, the good thing is that with test cases the sky is the limit. It is possible to raise just the obvious ones, and yet, it is also possible to show full expertise while staying within the confines of the original task.


Let's take an example: prepare test cases for a function that reverses a string. If you like, you can take a small break here, and think what test cases you'd come up with.




The first checkpoint are the requirements. Note that I never mentioned what should be done when reversing empty or NULL strings: should the function return the same value, throw an exception or assert? It was very rarely that I was asked questions about edge cases, but on those rare instances it was a major bonus for the interviewee.

Now, let's move to the basic functional test cases; there's the very obvious ones:


abc→cba
aba→aba

and slightly less so:


a→a
bbb→bbb
cC→Cc

Nevertheless, that was still the boring bit. For more excitement, let's turn to speed complexity:

  • Does the function perform quicker on palindromes?
  • What is the speed on huge strings; how does it scale with size? 
  • Does it perform better on multi-core machines?

Of course performance does not finish here, as we also have memory considerations
  • Does the function swap characters in place of uses a new copy?
  • What is the practical input size before we start swapping on typical hardware/VM?

However, we can go back to the functional tests, as we're not done there yet - we need to consider localization and special character sets:
  • What is the behaviour on UTF-16, UTF-8, UTF-32?
  • How does the function deal with special characters, such as newlines, and NULLs?

If we really want to be persistent about it, we can throw concurrency in, and consider what happens if we modify the string in parallel to calling it in a different thread (though you might argue that we cross into Software-Engineer-in-Test land here).

It's quite possible that we can squeeze out more, but if a candidate reached up to here, then they should already get the top prize: they know non-functional testing, exploratory testing, localisation, and concurrency (spoiler alert: never happened to me).
In any case, the intent is not to declare this exercise as the only way to filter candidates, but to demonstrate that with even the simplest task we can go far if need be. 


Do they understand system interactions?


Ability to recognise risky interactions is one of the main skills for test professionals. Ideally, I'd like to describe a system and ask what we should be worried about at a very high level. The problem, as always, is time - we can't devote more than 10 minutes, and that's not a lot of time to describe a system and discuss test strategy.

For this reason, I usually left this until face-to-face stage, but from time to time, if the previous stage went well, I'd describe a hypothetical system that relates to the person's previous experience. For example, if they have SaaS on their CV, I'd outline a licensing system that contains entitlements from different subscribers, and ask what typical faults they would explore. It's important not to overdo it, as such a Q&A can easily take up the rest of the call, but when it works well, it kills two proverbial birds with one stone: both establish SaaS credentials, and check ability to view systems from above.


CV/skillset discrepancy - still applies

Now, what I mentioned last time about CV/skillset discrepancy still applies. If someone scatters different skills in their CV, they're always fair game irrespective of what we are looking for. For this reason, I've been asking about automation, networking, security, Java and everything in between. As with developers, this works both ways - it can highlight important experience that could complement the team, or it can show CV inflation. Usually, I would not spend too much time on this: just a couple of questions per topic to do broad and shallow coverage.

In fact, everything else in the previous post also applies. We need to figure out if we can work together, so I might go for an open question that does not have a single answer, and see whether we can have a reasonable (but short) chat. To take an example at random: is it fine to concentrate on component testing only with microservices?
Also, this should be a non-confrontational experience for the interviewee, so I'll similarly present the role, the company, and let them ask any number of questions within reason.

Summary

Phone screening for QA candidates is not vastly dissimilar to developers, with the main accent shifted into ability to think of failure modes, and system interactions. At least one practical question during the call is an absolute must, and even the simplest test tasks can go very far.

Sunday, 31 May 2015

Hiring: Technical phone screening


Time has arrived to turn to another of my pet subjects: hiring. As any other hiring manager, I went through a long and winding road with varying results, which yielded a few practices, tips and tricks.

As they say at the end of a flight - I realise that you have a wide choice of other hiring guides and recommendations. The one difference here is that I'll be focusing as much on the end result as on how the process was improved on the way.


Why do phone interviews?

It's best to start from the beginning, and in this specific case, it would be the technical phone screening (I'm nimbly sidestepping the various HR phases and the actual procurement of CVs). Now, for most people this stage is taken for granted, but only a decade ago I did not belong to this enlightened group, and scheduled face-to-face meetings entirely based on resumes.

It took a couple dozens of disappointing interviews to realise two recurring points:
  • People can and will tailor keywords to the job spec.
  • Our and candidate's definition of being proficient in something may differ.

Now, before becoming too cynical: very often this misconception does not have any special intent on candidate's part. When they claim that they know C++ they might refer to writing university assignments twelve years ago, and using it in their second workplace eight years back. As I'm looking for a senior engineer, my perception of a C++ skillset is someone who can have a meaningful conversation with an expert and be aware of the latest standard.

(Of course, C++ is just an example - this happened to me with any number of skills, such as Python, Linux, networking, cryptography and so on and so forth)

Both of us are right in our own way, and it's very rare that someone would lie outright on their resume. The problem is that many applicants don't know what they don't know.




Coming to the main point: face-to-face interviews are expensive for the employer, but even more so for the candidate, who has to travel, potentially take a day off at their current job, and spend time on preparation.
Hence, inviting someone without knowing for a fact that they have a decent chance of getting an offer is simply impolite. And, knowing that based on CV alone is impossible.


What phone interviews include?

To understand better what phone screening should consist of, we need to step back and ask ourselves: what do we want to get out of this?

The answer is simple: we want to make sure that the candidate has a reasonable chance of making it through. In other words, if they show up in our office and we realise very quickly that they are not the right fit, then the screening has failed.

Extrapolating this to software engineers (I'll cover QA in a separate post), we need to tick off these points:

  • Can they code?
  • Is there a large discrepancy between what they think they know and what they actually know? 
  • Can we work together, i.e. is there a communication problem?

This is not a huge list, and 95% of the time a candidate that passes these would never be a straightforward rejection later on.

Can they code?


This is the main one. Developers are paid for developing. If they can't do that, then there's no point going much further.

It all sounds painfully obvious, but back in the day I wasn't asked to code even once before being invited over. On the other side of the fence, when we were omitting coding from our phone screening, we were getting candidates who knew theory (or scored a lucky match) but could not put it to practice.

To avoid this kind of situation, some people prefer sending assignments and asking to do those offline ahead of time. My issue with this approach is two-fold:
  1. This method doesn't shed much light on how productive a candidate is; they might have written the assignment in 15 minutes or 15 hours.
  2. If an offline assignment is big, then it's unfair: we ask for substantial time investment before we know there is chance of success. If it is small, then it gives opportunities for StackOverflow- or old-uni-friend-assisted answers, and does not give enough input.
Hence, my preference has been asking to code on a shared document during the call. Naturally, this also has its detriments; a person might freeze under pressure, and logistics become a bit more involved - we need to have a computer at both ends, and there is a slightly uncomfortable wait period, while the candidate is doing their best.

However, my personal experience showed that this method simply works. There was no single case where someone did coding well, and turned out to be an absolute no-go later on. And, vice versa, on a few occasions where I went against my better judgement and invited in people with shaky coding preliminaries, it ended in (figurative) tears.

The coding question is usually very simple, and something that anyone remotely proficient in programming can do within 5 minutes.


This could be reversing a string, determining whether a given list is a palindrome, or find the count of distinct characters. 
(NB: These are not the questions I'm asking today in case you're applying)

For a while, even the infamous FizzBuzz was filtering out a decent subset of candidates, until I decided to do add variety to the proceedings, and raise the bar a tiny bit.

Another benefit of the coding exercise is that it serves as a trampoline to the technical questions. If the code contains an odd construct or algorithm choice (e.g. using a temporary string for reversing), it gives an opportunity to probe theory.


Is there a CV/skillset discrepancy?

First, we need to explore the skills that matter to the job. If, let's say, we do development in Java and the interviewee has a strong Java presence in the CV, then the topic naturally merits a few questions. The trick here is to find questions which are not plainly obvious (i.e. demonstrate experience rather than preparation), and that will give a good insight into candidate's proficiency.

For example, in the Java case, I might ask how its garbage collection works. Here, there can be a wide spectrum of answers: from not knowing what this is to a Wikipedia-level answer covering different JVMs. If it feels like the answer is prepared and I simply played a King to candidate's Ace, I might ask an open question which would push them outside of the straight and narrow (e.g. when do you think your knowledge of garbage collection would be useful during day-to-day work?).

In some cases, even skills not entirely relevant to the job may be fair game. If someone has wide malware analysis experience, and I happen to know it too, we might talk about it. This can work both ways: in worst cases it can uncover CV padding, and in best cases it can show that they do have ability to understand subjects in-depth, and negate shortcomings elsewhere.

Note that discrepancy is not always so simple as know vs. don't know. It happened to me that we went very far in the process with candidates who had decent knowledge of the right technology, but (the crucial "but") they were certain that were far better than "decent", and possessed full mastery, which was simply untrue. This would have created a major problem later on: some people can adjust their self assessment, yet some will inevitably plunge in workplace conflicts when their technical superiority comes under fire.
The key point that it's easier (but not easy!) to acquire new skills than change personal attitude.



For this reason, I used to ask candidates to self-rate themselves on a given subject before drilling in. For example, we would have a problem if someone rated themselves as 8/10 on Python, and would not know what a Global Interpreter Lock is. On the other hand, if the self-rating were 2/10, then it wouldn't be as big an issue. Of course, it's yet another question whether 2/10 is good enough for the role, but at least we are on the same page.

With time, I stepped away a little bit from numerical self-assessment to relieve some of the psychological pressure that inevitably comes with phone interviews, and simply started asking how confident a candidate feels about a specific area. Usually, the certainty and the tone of the answer is a good enough indication.

Can we work together?

Unlike coding or technical questions, this one is very ambiguous. After all, is it even possible to figure out personal compatibility in 30 minutes?

My approach has been asking open-ended questions; someone with data storage in their CV might get "what is your opinion of the NoSQL movement? How does it compare to a full RDBMS?". 
Or, if someone has both Java and Python in the CV: what are your thoughts on strongly typed languages as opposed to duck typing?

(Of course, there's also technical aspect to this, as I'll find out on the way whether they know what NoSQL or duck typing are.)



They do not have a right or wrong answer, and most importantly, they ask for opinion rather than facts. The answers will tell whether the person is opinionated (or not), whether they can present and evaluate multiple positions, and consider someone else's viewpoint. To check the latter, sometimes, even if I agree with what comes on the phone, I might play the devil's advocate.

What do phone interviews exclude?

Now, for the usual flip side of the coin: what's to omit? Phone interviews should be kept reasonably short: both as a courtesy to the candidate, and as a mercy to our own calendar. Recall that we want to have a quick pre-filter, and two hours phone calls are anything but quick.

Here's my list:
  • In-depth questions, such as esoteric corners of a language standard, or a full list of flag names for a UNIX command. There's no point really: we need to figure out if a person is good enough to come over, not whether they are an absolute expert in a specific field. 
  • Review of past experience. Of course, previous jobs matter, but mostly as a path of gathering skills that might be of use for us. Hence, it makes more sense to evaluate the end result rather than the means of acquiring it. In face-to-face interview we might do things differently and go over the CV in detail, but there's not much time for it during a call.
  • HR questions, such as career expectations, main strengths etc. Again, time is short, and in any case: more often than not we'll be receiving stock answers that do not help with filtering the candidate. The right time for this will be the face-to-face stage (especially since these questions work far better when you can see each other).


Interview - not interrogation


Last, but not least, remember: interview is for both parties; the ideal time split between questions is two-thirds company, one-third candidate. As many others stated: phone interview is often the first real impression the candidate will have of your company. If he/she is capable and has the right skills (exactly the type you want to hire), it's very likely that they'll have a choice of employers, so this impression matters a lot.

To that purpose, I tend to follow a number of rules:
  • Never give a verdict during the interview, even if it went great, and the person you talked to on the phone is the living amalgamation of everything you're looking for from a software engineer. Why? You never know what might happen tomorrow: maybe the position will be pulled from underneath you, maybe someone from within the company will apply, and maybe you'll show some of the answers to the team, and they'll notice a gap that you've missed. There are few worse experiences than guaranteeing a face-to-face interface and then reneging on that promise. A special case of that is:
  • Never cut the interview short - even if it's evident after ten minutes that the process won't go much further. This immediately gives a negative answer, and thus violates the previous rule. Also, it is just a matter of basic courtesy.
  • Always close off with the potential next steps and response ETA. As per the previous point: this happens even if deep in my heart I know that we will not be meeting. Regardless, I'll explain how face-to-face interview would run should we get there, and by what date they will hear from us.
  • Always allow interruptions and dialogue. It's not just about satisfying curiosity and giving the interviewee full input (although this matters a lot of course!). In many cases the questions you hear will give a far better insight than the ones you ask.
    For example, years ago, during a senior dev hiring round, I was asked about specific JavaScript sandboxing challenges, which happened to be exactly the task we were tackling ourselves. Needless to say it propelled the candidate up the list.
    Note that you can interrupt too, especially if the candidate goes on a tangent or takes time to impress on topics you haven't asked (this happened to me a number of times). 
  • Never lead off with technical questions. I always prefer to ease in by first taking about the company, the role and why we are looking for this particular profile. This makes the process two-sided, and makes the other party less nervous.





  • Don't sound like a broken record. In other words, when talking about the position, don't say the same words in each call. I know - it's hard, especially once we get going and run a couple of calls each day. However, a brief 5 minute preliminary look at the CV always gives a few pointers and removes that conveyor belt feeling.
    For example, rather than saying "and we use Java for our automation framework" , I might say "and, as with your Initech experience five years ago, we use Java for automation. This is why this part of your career will be particularly interesting for us". 
    This all goes back to first impressions: showing that you carefully looked through the CV is a signal of genuine personal interest, and might be the little gesture that will push the candidate in the right direction.


Summary

Good technical phone interviews are the cornerstone of a good hiring strategy. Without them, you either end up wasting your and candidate's time, or rejecting potential matches through CVs alone. Getting them right is hard but rewarding: it consists of laser focus on our pre-filtering points (coding, CV truthfulness, and personal compatibility) and giving the right first impression to the candidate.

Sunday, 24 May 2015

Code comments: explaining the 'why'

While I'm on the subject on code reviews. it's hard to get past one of my favourite subjects: code comments.

You might wonder here whether comments justify having a full blog post. After all, it's quite simple: add a line in plain English (or whichever language tickles your fancy at a given moment), and move on to doing real coding.

My main bugbear with review comments is not so much their absence or presence, but rather their purpose: in many cases, they explain the obvious and leave the complicated as an exercise for the reader (to borrow a phrase from one of my university tutors).

Let me dig out an example:
#include <iostream>
#include <string>
 
using namespace std;
 
bool TestPalindrome(string::const_iterator forwardIt, string::const_iterator backIt)
    {
    for (; forwardIt < backIt; ++forwardIt, --backIt)
       if (*forwardIt != *backIt)
         return false;
    
    return true;
    }
 
bool ProcessTestCase()
    {
    string inputStr;
    cin >> inputStr;
 
    auto forwardIt = inputStr.cbegin();
    auto backIt = --inputStr.cend();
    
    for (;forwardIt < backIt; ++forwardIt, --backIt)
       {
       if (TestPalindrome(forwardIt + 1, backIt) || TestPalindrome(forwardIt, backIt - 1))
          return true;
       if (*forwardIt != *backIt)
          return false;
       }
    return true;
    }
 
int main(int argc, char *argv[])
    {
    ios_base::sync_with_stdio(false);
    size_t testCaseCount(0);
    cin >> testCaseCount;
    for (size_t i = 0; i < testCaseCount; ++i)
      {
      if (ProcessTestCase())
         cout << "YES";
      else
         cout << "NO";
      cout << endl;
      }
    return 0;
    } 

Now, at this point, you don't know what this code is for and how it does it. Of course, a bit of reverse engineering is a nice past-time for a lonely afternoon, but let's say that we'd really like to take a shortcut and see some comments.

So, here they are:
/* Determine whether a set of inputs can be converted to 
   a palindrome by removing a character
*/

#include <iostream>
#include <string>
 
using namespace std;
 
bool TestPalindrome(string::const_iterator forwardIt, string::const_iterator backIt)
    {
    for (; forwardIt < backIt; ++forwardIt, --backIt)
    // If character mismatch, return false
       if (*forwardIt != *backIt)
         return false;
    
    // Loop finished, return true
    return true;
    }
 
bool ProcessTestCase()
    {
    string inputStr;
    cin >> inputStr;
 
    // Set iterators to the start and end of string
    auto forwardIt = inputStr.cbegin();
    auto backIt = --inputStr.cend();
    
    for (;forwardIt < backIt; ++forwardIt, --backIt)
       {
       // Check for palindromes
       if (TestPalindrome(forwardIt + 1, backIt) || TestPalindrome(forwardIt, backIt - 1))
          return true;
       // If iterators are not equal, exit the loop
       if (*forwardIt != *backIt)
          return false;
       }
    return true;
    }
 
int main(int argc, char *argv[])
    {
    ios_base::sync_with_stdio(false);
    size_t testCaseCount(0);
    // Read test case count
    cin >> testCaseCount;
    for (size_t i = 0; i < testCaseCount; ++i)
      {
      // Print out result
      if (ProcessTestCase())
         cout << "YES";
      else
         cout << "NO";
      cout << endl;
      }
    return 0;
    }

The top level comment should take us forward - it at least explains what the purpose of the code is. But what about the others? It's hard to argue with their truthfulness, but they are kind of obvious.

Here, I'm approaching the crux of the message: often, people put Mathematician's answers as comments. I.e. the information is correct, it is in the right place, and yet, it does not help whatsoever.
The right comments add information that's absent in the code itself, and in our example, the code already tells that we read the test case count, print out the results, and exit the loop.

Of course, it's easier said than done. Not everyone can just flick a switch, get an out-of-body experience, and know what everyone else doesn't know. My personal approach to that was adding comments in two phases:
  1. While writing, when knowingly adding complex code.
  2. After finishing coding, and before submitting it for review. There's always a natural break between those two when automated tests are doing their worst. This is the right time to take a step back, read the code again, and pick up quirks that seemed natural on the first pass, but do not longer look that way after a few minutes away from the screen.

Here's the same code example with better comments:
/* Determine whether a set of input can be converted to 
   a palindrome by removing a character
*/

#include <iostream>
#include <string>
 
using namespace std;
 
bool TestPalindrome(string::const_iterator forwardIt, string::const_iterator backIt)
    {
    for (; forwardIt < backIt; ++forwardIt, --backIt)
       if (*forwardIt != *backIt)
         return false;
    
    return true;
    }
 
bool ProcessTestCase()
    {
    string inputStr;
    cin >> inputStr;
 
    auto forwardIt = inputStr.cbegin();
    auto backIt = --inputStr.cend();

    // Run two counters: from the start and end of string. 
    // If they point to different characters,
    // check whether removing one of them yields a palindrome. 
    // If not, we cannot convert the input
    // to palindrome by a single character removal
    for (;forwardIt < backIt; ++forwardIt, --backIt)
       {
       if (TestPalindrome(forwardIt + 1, backIt) || TestPalindrome(forwardIt, backIt - 1))
          return true;
       if (*forwardIt != *backIt)
          return false;
       }
    // If inputStr itself is a palindrome, 
    // we can simply remove a character from the middle,
    // so return true here.
    return true;
    }
 
int main(int argc, char *argv[])
    {
    ios_base::sync_with_stdio(false);
    size_t testCaseCount(0);
    cin >> testCaseCount;
    for (size_t i = 0; i < testCaseCount; ++i)
      {
      if (ProcessTestCase())
         cout << "YES";
      else
         cout << "NO";
      cout << endl;
      }
    return 0;
    } 

Comparing the two examples one against the other:
  • No more comments which tell what the line below does, such as "Set iterators to the start and end of string". If the reader knows C++, they won't need it. If the reader does not know C++, then they'll need far more than that.
  • New comment explaining the main algorithm (lines 27-31).
  • Comment highlighting a specific edge condition (lines 39-41).
The last one exemplifies the difference quite well. If someone annotates code as per the first example, they would never consider commenting what return true does. But, the important bit is not what it does, but why it's there, and why it does not return false.

Coming back to the two-phase approach: if I need more than a minute to think of a specific code line, then it is complex, and probably merits a comment. In this specific case, the return true took me a couple of minutes, so it got a comment as its chaperon.
And, to close a circle, let's mention code reviews. If someone needs clarification during review, then nine times of ten the answer should manifest itself within the code.

So, here we are: comments should explain 'why' rather than 'what', and they should be read and written by taking a 3rd person view.

However, it's also possible to take things into the other extreme. If you find that comments overwhelm the content, then it's quite possible that the content itself is the problem.

For example, I could add a comment for the line below:
res = itertools.product([x for x in myList if x in y else calcValue(x)], anotherList)
like this:
# For all values not in the intersection of x and y, recalculate value, and then create a product with another list
res = itertools.product([x for x in myList if x in y else calcValue(x)], anotherList)
or I could just rewrite the code to make it more explicit:
newList = []
for x in myList:
   newValue = x if x in y else calcValue(x)
   newList.append(newValue)
res = itertools.product(newList, anotherList)
The last variant is more readable despite not having a single comment.