tag:blogger.com,1999:blog-45372339275176084172024-02-20T09:34:33.759-08:00Actual QAMichael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.comBlogger30125tag:blogger.com,1999:blog-4537233927517608417.post-28037502586336844712022-08-19T12:34:00.005-07:002022-08-19T12:34:50.657-07:00Yes, They Did Let Us Choose<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0ddA3ptRIYe3U21CKiSZs5Hy2pZUN2W-M0SuxYo5PqhOkTv21erP_Zr-2EYAR7r99yFQ79-DKKaP7sqSuQI_5GLEVZ_FQX8ngFSbi9f0vemqiQjVrW_hmIpIEg9l43tDl-Hv_9HYwMR5-WPk1BD60Gc0R--uLeWmumio1b7XOI_sm_c6e3bhoRqQE6g/s3007/IMG_8681.JPG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1867" data-original-width="3007" height="199" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0ddA3ptRIYe3U21CKiSZs5Hy2pZUN2W-M0SuxYo5PqhOkTv21erP_Zr-2EYAR7r99yFQ79-DKKaP7sqSuQI_5GLEVZ_FQX8ngFSbi9f0vemqiQjVrW_hmIpIEg9l43tDl-Hv_9HYwMR5-WPk1BD60Gc0R--uLeWmumio1b7XOI_sm_c6e3bhoRqQE6g/w320-h199/IMG_8681.JPG" width="320" /></a></div><br /><p></p>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-5457206178390000002022-08-14T04:32:00.015-07:002022-08-19T16:56:46.860-07:00A Security Hole<p>In the '90s I was a QA tester at Macromedia, and one thing I tested was Shockwave. Shockwave was Macromedia's proprietary web browser plug-in for playing interactive multimedia. That was one of the very first plug-ins for Netscape Navigator (and for that matter, one of the first ActiveX controls for Internet Explorer, but let's not get bogged down).</p><p>One day we added a new feature to probe the keyboard, so you could figure out which keys were currently being held down. This is very useful for arcade-style games, when you need to know (for instance) if the left shift key and the left function key and the right control key are being held down at the same time. This was new; Shockwave couldn't do that before.</p><p>Anyway, I was assigned to test this thing. Which I did, and it passed with flying colors. It definitely worked as advertised. So it was included in the next rev of the plug-in / ActiveX control.</p><p>But at some point, after we had already shipped the feature, something nagged at me. I had a worrisome thought that maybe I missed something big.</p><p>So I came to work and verified my fear -- any piece of Shockwave content currently running in your web browser was capable of probing the keyboard <i>even if the keyboard focus was somewhere else</i>.</p><p>This meant that your little Shockwave game where you click on the kittens (or whatever) could be a nefarious keylogger, capturing everything you typed anywhere in the browser, whether that window had the current focus or not.</p><p>I was, of course, horrified that I missed this huge, gaping security hole. So I reported the issue immediately. And then engineering asked me for a user scenario to illustrate how bad it really was. In other words, <i>they needed an explanation as to how a keylogger could pose a problem for our users.</i></p><p>Eventually I managed to convince management that this bug had to be fixed ASAP. So they threw some engineering time at it, and the fix got merged into the codebase. But there it languished, because they decided to roll it out with the next scheduled update.</p><p><i>For weeks</i> I arrived at work each morning, checking the external mailing lists to see if any of our customers had noticed the aforementioned huge, gaping security hole.</p><p>Eventually, I saw one post from a user who noticed the situation but hadn't grasped its security implications. And when no other user replied to that message, it seemed to disappear with only a shrug.</p><p>I think we shipped a dot release about a week later, which included the bugfix. And then I could finally relax about it.</p>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-14196784970685335002020-08-13T04:01:00.012-07:002020-08-13T04:55:40.973-07:00EULA bugs<p>Time was, most days you’d get an installer to test. Sometimes even two or three installers in a single day. And you had to run those installers on lots of lab machines. Which means, over time you clicked a lot of Install buttons.</p><p>The very first thing every installer did was brandish its End User License Agreement to the customer, which was a company’s carefully-written, bacon-saving liturgy. Whereupon you had to click the checkbox or radio button to indicate that, yes, you had indeed just read and digested the entire document and determined it to be not only satisfactory but also legally binding.</p><p>One day at the big security vendor for which you were working, it occurs to you that there could be bugs hiding in that EULA — that is, in the text itself. It being your job to find bugs, you start reading the thing. Well, skimming really. But you’re at least running your eyes over the sentences.</p><p>Which is when you notice that the postal address for customer service in the European Union is munged, along with some of the surrounding text. So you file a bug report, which wends its way through the Legal department and eventually emerges some days later along with a new installer sporting a regulation EULA which contains the correct address. As it turns out, it had been a copy & paste error by someone in between the lawyers and the person who built the installer.</p><p>Some time later, your department’s test plans are changed to incorporate diffing the installer EULA against the one blessed by Legal.</p><p>Lessons:</p><p>1. Bugs are hiding anywhere that people don’t look.</p><p>2. Never assume that business-critical issues belong to someone above your pay grade — like someone in, say, the Legal department.</p><p>3. It’s safest when responsibility for deliverables is shared. Either you have overlaps, or gaps. Your choice.</p>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-12498325422769200022020-07-14T16:07:00.003-07:002020-07-14T16:40:32.411-07:00There Is But One Prod<blockquote class="tr_bq">
There are only two hard things in Computer Science: cache invalidation and naming things.</blockquote>
<blockquote class="tr_bq">
— Phil Karlton</blockquote>
<br />
Let's say that whenever your software company ships its product to actual customers, it is promoted to a branch named "Prod" or something like that.<br />
<br />
Let's also say your team builds software whose users are all internal to the company. That is to say, it is never publicly released. None of your company's customers are ever going to see it.<br />
<br />
In such a situation, don't let your team name their own release branch Prod also. Prod (or its equivalent) should always mean one thing and one thing only: this software is available to actual customers.<br />
<br />
If your internal dev team is releasing its internal tool on a branch called "Prod", it gets confusing for other people. It happens. And this confusion is totally avoidable. It's an unforced error. Call your branch something else.<br />
<br />
There is but one Prod, and thou shalt have no other Prods before it.Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-89389446584582032492017-09-19T17:49:00.008-07:002022-08-19T16:57:42.109-07:00Welcome to the InterviewJob interviews are inherently stressful, because the stakes are so high. After all, getting a job is a life-changing event.<div><br /></div><div>For some reason, a lot of interviewers seem to believe that the stress is somehow beneficial to the process, so they go out of their way to make things even more stressful.<br />
<br />
I disagree. I find it counterproductive for both parties because it interferes with memory and reasoning skills, making it harder to answer questions.<br />
<br />
That said, let me explain a few things:<br />
<ol>
<li><b>I am going to interrupt you. </b>It doesn't mean that things are going poorly. It's just that our time is finite. So don't let the interruptions bother you.</li>
<li><b>If something isn't totally clear, ask me to repeat the question or rephrase it. </b>Make sure that you understand my questions. Sometimes I can misspeak, or choose the wrong words. Also, different companies can use different names to refer to the same things. It could be that I'm using an unfamiliar term for something that you know by a different name.</li><li><b>If I ask you to write code, it doesn't have to compile.</b> I don't care about syntax, like braces or semicolons. You can use any programming language that you want. You can even write pseudocode. I just want to understand your program's logic.</li><li><b>You don't have to answer right away.</b> It's okay to think before answering. And it's okay to pause in the middle of an answer. It's even okay to pause in the middle of a sentence.</li><li><b>If you get stuck, it's okay to ask for hints.</b> Sometimes you might need a little push in the right direction.</li>
<li><b>Sometimes your best answer might actually be <i>"I don't know."</i></b> I may be asking a series of questions to probe the perimeter of your knowledge. It's okay not to know every answer.</li><li><b>If a better answer pops into your head later on, go ahead and tell me.</b> We can rewind to a previous question.</li>
<li><b>I don't have a secret question which you must answer correctly.</b> Some interviewers have a pet question that everyone has to get right. If you totally bomb on a question, don't worry. It's not an automatic fail.</li><li><b>You're not required to ask me questions.</b> Every interview website suggests that you ask your interviewer questions to show interest. I don't care if you have questions. If you do, that's fine. But let's save them until the end.</li>
</ol>
</div>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-52539218702037631312014-04-25T09:46:00.000-07:002014-04-25T09:46:02.231-07:00Feature Idea for Blogging Software<p>When presenting the user with the list of tags applied to a post, how about you omit any tag that has only 1 member (i.e. the post you just read)?</p>
<p>Clicking on such a tag is a total waste of time.</p>
<p>Calling the patent attorney now...</p>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-86673668813485062182014-04-18T17:13:00.002-07:002014-04-18T17:16:51.251-07:00Python speech synthesis on OS XI know, this is cheating. But it's fun.<br />
<br />
<pre>def speak(*args):
from subprocess import Popen, PIPE
cmd = ["/usr/bin/say"]
cmd.extend(args)
Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
</pre>
Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-8118375957506656032014-04-08T17:49:00.001-07:002014-04-08T17:58:01.263-07:00Copying text via Python on OS X (revisited)Let's try that again.<br />
<br />
<pre>
import Cocoa
pb = Cocoa.NSPasteboard.generalPasteboard()
def get_clipboard_text():
"""Get the text contents of the system clipboard.
@return (string)
"""
return pb.stringForType_(Cocoa.NSPasteboardTypeString)
def copy_text_to_clipboard(text):
"""Copy the supplied text to the clipboard.
@param text
"""
pb.declareTypes_owner_([Cocoa.NSPasteboardTypeString], None)
pb.setString_forType_(text, Cocoa.NSPasteboardTypeString)
</pre>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-49989065913940398422012-12-12T17:56:00.000-08:002012-12-12T17:56:02.990-08:00Copying text via Python on OS XIt took me an embarrassingly long time to figure out why I was getting an extra newline character appended to the copied text. (It was because I forgot to include the -n parameter to echo.)<br />
<br />
<pre>from subprocess import Popen, PIPE
def copy_to_clipboard(self, text):
# equivalent to: echo "text" | pbcopy
p1 = Popen(['/bin/echo', '-n', text], stdout=PIPE)
p2 = Popen(['/usr/bin/pbcopy'], stdin=p1.stdout, stdout=PIPE, stderr=PIPE)
p2.communicate()
def get_clipboard_text(self):
return Popen(['/usr/bin/pbpaste', '-Prefer', 'txt'], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate()[0]
</pre>
Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-22271416616141523602012-04-20T15:01:00.002-07:002012-04-25T23:23:56.109-07:00Igpay Atinlay<pre><b>def pig_latinize(text):
'''
Returns the Pig Latin version of the supplied English text.
@return (string) Pig Latin
'''
import string
new_text = ''
for word in text.split():
punctuation_mark_begin = ''
punctuation_mark_end = ''
if word[-1] in string.punctuation:
punctuation_mark_end = word[-1]
word = word[:-1]
if word[0] in string.punctuation:
punctuation_mark_begin = word[0]
word = word[1:]
all_caps = word == word.upper()
title_caps = word[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
word = word.lower()
new_word = None
m = len(word)
if m < 3 or word in ['and', 'the']:
new_word = word + 'kay'
else:
vowel_offsets = [word.find(v) for v in 'aeiouy' if (word.find(v) != -1)]
m = 0 if (len(vowel_offsets) == 0) else min(vowel_offsets)
if m == 0:
new_word = word + 'way'
else:
new_word = word[m:] + word[:m] + 'ay'
if all_caps and (len(new_word) > 1):
new_word = new_word.upper()
elif title_caps:
new_word = new_word[0].upper() + new_word[1:]
new_text += punctuation_mark_begin + new_word + punctuation_mark_end + ' '
return new_text[:-1]</b></pre>
<br />
<br />
INKAY ONGRESSCAY, ULYJAY 4KAY, 1776WAY Thekay unanimousway Eclarationday ofkay thekay irteenthay unitedway Atesstay ofkay Americaway Enwhay inkay thekay Oursecay ofkay umanhay eventsway itkay ecomesbay ecessarynay orfay oneway eoplepay tokay issolveday thekay oliticalpay andsbay ichwhay avehay onnectedcay emthay ithway anotherway andkay tokay assumeway amongway thekay owerspay ofkay thekay earthway, thekay eparatesay andkay equalway ationstay tokay ichwhay thekay Awslay ofkay Aturenay andkay ofkay Ature'snay Odgay entitleway emthay, akay ecentday espectray tokay thekay opinionsway ofkay ankindmay equiresray atthay eythay ouldshay eclareday thekay ausescay ichwhay impelway emthay tokay thekay eparationsay.
<br />
<br />
<i>Oodgay enoughway.</i>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-76569267088586072912011-11-17T00:20:00.001-08:002011-11-17T00:28:27.999-08:00str() is the DevilRecently I discovered the folly of using str() within Python 2.7 scripts. My program would ingest arbitrary UTF8 text from outside sources and then try to print it to a file, only to crash with a UnicodeDecodeError or UnicodeEncodeError exception. In good time, I realized how to do it The Right Way and converted all my str() calls to unicode().<br />
<br />
But the error kept occurring. It took me quite a while to remember that<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">foobar = ''</span><br />
<br />
is functionally equivalent to<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">foobar = str()</span><br />
<br />
This problem shouldn't happen in Python 3, since all strings are Unicode by default there.Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-15475939635501936302011-09-15T19:43:00.000-07:002011-09-15T19:47:10.416-07:00How to turn a list outside-in<br />
<code></code><br />
<pre><code>def turnListOutsideIn(aList):
"""
Returns a new list containing the 1st element, then the last,
then the 2nd, then the next-to-last, etc.
"""
# create a reversed copy of the original list
revList = list(aList)
revList.reverse()
# zip them together as a list of tuple pairs
zippedList = zip(aList, revList)
# flatten the list
flatList = [inner for outer in zippedList for inner in outer]
# return the first half of the list
return flatList[0:len(aList)]
digits = range(10)
print outsideInList(digits)
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
</code></pre>
Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-1151187539333836922011-09-11T12:35:00.001-07:002020-08-13T04:18:51.431-07:00September 11th, 2001Today is the 10th anniversary of the attacks on the World Trade Center, Pentagon, and wherever it was that the hijackers of United Airlines flight 93 had intended to strike.<br />
<br />
My girlfriend and I, living in Los Angeles, were asleep at 6:50am when a relative called and made us turn on our television. We were watching live coverage when the second plane struck the North Tower.<br />
<br />
After the shock wore off, I decided I should go to work. I was a software tester at a major security vendor. I figured that in case there were a cyberwarfare component to the attack, I wanted to be on hand to help out in any way that I could. But my manager sent us all home, the handful who showed up.<br />
<br />
So I went back home and spent the rest of the day glued to the TV, like everybody else.Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-3290616696988172622011-07-26T12:43:00.000-07:002011-07-26T12:47:25.636-07:00Don't use JSON for config files.Why not? Because JSON does not allow comments.<div><br /></div><div>That is all.</div>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-1808556654331718282011-05-14T09:00:00.000-07:002011-05-15T04:21:07.818-07:00Process pipelines in PythonIt took me long enough to figure this out, I figure somebody else might benefit from my effort.<br /><br />When you're working in Python and you wish to launch an external program and capture the output, there's an easy solution: you use <b>popen</b> from the <b>subprocess</b> module. All well and good. But suppose you need to fire up a pipeline of external programs, with the output of the first program being piped to the input of the second, and so on. Something like this:<div><br /></div><div><div><span class="Apple-style-span" style="font-family:'courier new';"><b><span class="Apple-style-span" style="font-size:small;">$ </span><span class="Apple-style-span" style="font-size:small;">cat /etc/passwd | grep -E '^root:' | tr "a-z" "A-Z"</span></b></span></div><div><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:small;">ROOT:*:0:0:SYSTEM ADMINISTRATOR:/VAR/ROOT:/BIN/SH</span></span></div><div><br /></div><div>The answer is still popen, but things get a little complicated if you want to solve the general problem of hooking up n processes into a pipeline. Here's my solution.<br /><br /><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:small;"><div><div><div><span class="Apple-style-span" style="font-size:small;"><div><span class="Apple-style-span" style="font-size:x-small;">def launchProcessPipeline(cmdList):</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">import fcntl, time</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">totalCmds = len(cmdList)</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">if totalCmds > 0:</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">procs = []</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">try:</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">for i in range(totalCmds):</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">currPipe = None</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">if i > 0:</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">currPipe = procs[i-1].stdout</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">procs.append(subprocess.Popen(cmdList[i], stdout=subprocess.PIPE, stdin=currPipe))</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;"># set stdout file descriptor to nonblocking</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">flags = \</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">fcntl.fcntl(procs[-1].stdout.fileno(), fcntl.F_GETFL)</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">fcntl.fcntl(procs[-1].stdout.fileno(), fcntl.F_SETFL, (flags | os.O_NDELAY | os.O_NONBLOCK))</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">except:</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">raise</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">if len(procs) == totalCmds:</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">return procs[-1]</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">return None</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:x-small;">def pollProcess(proc):</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">import select</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">output = None</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;"># wait 1 millisecond and check whether proc has written anything to stdout</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">readReady, _, _ = select.select([proc.stdout.fileno()], [], [], 0.001)</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">if len(readReady):</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">try:</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">for line in iter(proc.stdout.readline, ""):</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">if output is None:</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">output = ''</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">output += line</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">except IOError:</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;"># Ignore any I/O errors reading from the pipe, which are infrequent but not rare.</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">pass</span></div><div><span class="Apple-style-span" style="font-size:x-small;"><br /></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-size:x-small;"> </span></span><span class="Apple-style-span" style="font-size:x-small;">return output</span></div></span></div></div></div></span></span><div><br /></div>And here's how you call it:</div><div><br /></div><div><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style=" ;font-size:small;">cmdlist = [</span></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:small;"> </span></span></span><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:small;">['cat', '/etc/passwd'],</span></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:small;"> </span></span></span><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:small;">['grep', '-E', '^root:'],</span></span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:small;"> </span></span></span><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:small;">['tr', 'a-z', 'A-Z']</span></span></div><div><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:small;">]</span></span></div><div><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:small;">proc = launchProcessPipeline(cmdlist)<br />print pollProcess(proc)</span><span class="Apple-style-span" style="font-size:small;"><br /></span></span></div><div><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style=" ;font-size:small;"><br /></span><span class="Apple-style-span"><b><span class="Apple-style-span" style="font-size:small;">>>> </span></b><span class="Apple-style-span" style="font-size:small;">ROOT:*:0:0:SYSTEM ADMINISTRATOR:/VAR/ROOT:/BIN/SH</span></span><span class="Apple-style-span"><span class="Apple-style-span" style="font-size:small;"><br /></span></span></span><br /></div></div>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-72795561176766170412010-10-24T13:46:00.001-07:002010-10-24T13:58:25.930-07:00How Not to Lie on Your Resume, Part 1The other day we were interviewing a prospective Senior QA Analyst. I noted with satisfaction that at his present job (going back two years), he listed Perl under "Programming Languages". This makes me happy because I use Perl regularly to automate testing tasks.<div><br /></div><div>When it came time for me to ask questions, I posed a simple task: <i>Write a function in Perl which takes a multiline string as input, and returns an array of strings consisting of each valid IP address from the input.</i></div><div><br /></div><div>He didn't know where to start. When I tried to jog his memory by asking whether a regexp would be a good way to extract the IPs, he admitted being <span style="font-weight:bold;">completely unaware of the term <span style="font-style:italic;">Regular Expression</span></span>. Oh my.<br /><br />He finally took a stab at it by walking the input string one character at a time, looking for the first period and then backtracking to see if the previous char was a numeral. And then capturing subsequent periods and digits, until finally counting the number of captured periods and digits. A truly terrible solution.</div><div><br /></div><div>So, tip #1 for job seekers who don't want to look like a fraud in your interview:<br /><br /><i>Under the description for your current job, only list programming languages you actually do know.</i></div><div><br /></div>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-54284373759997832242010-08-07T13:00:00.000-07:002010-08-07T13:09:50.356-07:00Fix NeverBack in the day, the <span class="blsp-spelling-error" id="SPELLING_ERROR_0">Macromedia</span> Director team used a clunky <span class="blsp-spelling-error" id="SPELLING_ERROR_1">FileMaker</span> Pro database to store its bug reports. It ran on a <span class="blsp-spelling-error" id="SPELLING_ERROR_2">PowerMac</span> in the test lab with a handwritten sign threatening hellfire and damnation against anyone who so much as <span style="font-style: italic;">touched</span> the machine. This was because the whole team -- the whole project, really -- relied on that system's continued health.<br /><br /><span class="blsp-spelling-error" id="SPELLING_ERROR_3">FileMaker</span> Pro was kind of an unusual choice for a mission critical database in the mid-to-late 1990s. Those of you familiar with that general vintage of <span class="blsp-spelling-error" id="SPELLING_ERROR_4">FileMaker</span> are probably wondering why we bothered to put it with its limitations (slow search & retrieval, record locking issues, long recovery delay after db crashes, etc).<br /><br />One reason was historical. Every Director <span class="blsp-spelling-error" id="SPELLING_ERROR_5">bugbase</span> since version 3.1.3 was built in <span class="blsp-spelling-error" id="SPELLING_ERROR_6">FileMaker</span>, so switching to another DBMS would have required migrating scripts or maintaining parallel systems for researching old bugs.<br /><br />But the real reason was simpler: <span class="blsp-spelling-error" id="SPELLING_ERROR_7">FileMaker's</span> client <span class="blsp-spelling-error" id="SPELLING_ERROR_8">UI</span> was readily customizable, and the Director team took full advantage of that capability.<br /><br />I wish I had a screenshot of that <span class="blsp-spelling-error" id="SPELLING_ERROR_9">UI</span>. The main screen was jam-packed full of little buttons and <span class="blsp-spelling-error" id="SPELLING_ERROR_10">textfields</span>, reflecting years of accreted process improvements. It was ugly. But more importantly it was functional, and highly optimized to our team's <span class="blsp-spelling-error" id="SPELLING_ERROR_11">workflow</span>.<br /><br />Something that still tickles me about the Director <span class="blsp-spelling-error" id="SPELLING_ERROR_12">bugbase</span> was one shimmering feature of brutal honesty. It was the scrub team's job to prioritize bugs. They had their choice from a drop-down list of defined dispositions: Fix By Alpha, Fix By Beta, Fix By Ship, Fix Future Release... and a very special option called <i>Fix Never</i>.<br /><br />That was reserved for bugs which were trivial, moot, utterly lacking in necessity. Also for bugs that couldn't possibly be fixed without destabilizing the entire product. Either way, nobody on the scrub team was capable of imagining a day that the effort to fix it could ever be judged worthwhile. Hence: Fix Never.<br /><div><br /></div><div>I haven't seen a bug database with such honesty since.</div>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-80465162410930091932010-03-03T10:00:00.000-08:002010-03-21T21:36:52.432-07:00Coke v. Pepsi<div><i>"Which are you more proficient with: Mac or PC?"</i></div><div><br /></div><div>It bothers me somewhat whenever this comes up in a job interview.</div><div><br /></div><div>Beginning with my very first job in the software industry, I have maintained equal proficiency in Macintosh and Windows. As a matter of fact, during most of my working life I have had both a Mac and a PC on my desk.</div><div><br /></div><div>And that's how I answer that question.</div><div><br /></div><div>So then about half of these interviewers choose to doggedly press the issue:</div><div><br /></div><div><i>"But which one do you <b>personally</b> prefer, Mac or PC?"</i></div><div><br /></div><div>The problem is, most of the interviewers who ask this followup question don't seem to listen to my answer beyond the first four words.</div><div><br /></div><div>"<b><i>I prefer the Mac</i></b>, because the user interface is more consistent and it's BSD UNIX under the hood. So I have a full-fledged command line shell with all the GNU utilities, plus Java, Ruby, Perl, and Python pre-installed."</div><div><br /></div><div>After they hear me say the magic word "Mac", they appear to tune out. I have answered incorrectly. As I continue to deliver the rest of my answer, I can see it on their faces: <i>Oh, he's a Mac guy. Probably not very technical.</i></div><div><br /></div><div>For the record, I have owned the following computers in my lifetime:</div><div><ul><li>Timex Sinclair ZX81</li><li>Apple //e</li><li>Apple IIGS (limited edition "Woz" case) with Transwarp card</li><li>Mac IIci</li><li>NeXT Turbo Color Workstation</li><li>iMac DV SE</li><li>iBook 800MHz</li><li>PowerBook G4 1GHz</li><li>5 or 6 PCs running Windows, Linux, or OpenBSD</li></ul></div><div>Perhaps I should start claiming to prefer the NeXT. That might throw a monkeywrench into the works.</div><div><br /></div>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com1tag:blogger.com,1999:blog-4537233927517608417.post-72477498407624331712009-12-18T13:39:00.000-08:002009-12-18T13:50:12.395-08:00Numeric Input FieldsHere's a simple tip for testing numeric input fields: <b>Try octal numbers.</b><div><br /></div><div>Provide input in the form of digits 0-7, with the first digit always being 0. Hence 011 is a valid octal number (decimal equivalent of 9).</div><div><br /></div><div>If your app treats "011" as 9, then there's probably a call to ParseInt() which omits the numeric base argument. In some languages, ParseInt() guesses the likely numeric base for the provided input if one isn't specified. So 011 becomes 9.</div>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com1tag:blogger.com,1999:blog-4537233927517608417.post-66512084502053599832009-04-13T13:13:00.000-07:002009-04-13T13:48:42.525-07:00QA >> TestingJohn Overbaugh <a href="http://thoughtsonqa.blogspot.com/2009/04/software-testing-is-your-quality.html">discussed</a> the proper role of the QA team last week:<br /><blockquote style="font-style: italic;">In a recent thread on the Yahoo Agile Testing Group (about continuous release), I noticed more than one poster saying something like "I tell management about an issue, and let them decide." This reactive approach to QA has been a pet-peeve of mine. It smacks of cop-out to me, and I've seen it develop into a victim mentality among many testers—myself included.<br /></blockquote>He went on to explain some of the other (non-testing) responsibilities of an effective QA organiztion. After having read John's post, I'd like to clarify <a href="http://actualqa.blogspot.com/2009/02/what-if.html">something I wrote earlier</a>:<br /><blockquote><span style="font-style: italic;">Your job is find bugs, report them, and make sure that the managers who run the project understand your bugs. It's their job to decide which ones get fixed, and which get deferred. As long as they understood your bug and its implications, it's their call whether to defer. Your job is done at that point.</span><br /></blockquote>In context, I was only talking about maintaining a professional attitude when a bug of yours gets deferred. But after reading John's piece, I became concerned that someone would get the wrong impression from reading my post. So to be clear, I agree with John's thesis:<span style="font-weight: bold;"><br /></span><blockquote><span style="font-weight: bold;">Testing by itself is an ineffective Quality Assurance strategy.</span></blockquote>QA is much more than testing. Or at least it should be.Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-35015679424239994952009-04-13T02:25:00.000-07:002009-04-13T18:15:48.920-07:00Black-Boxing the Simple AppNearly every QA interview includes a roleplay scenario where you have to explain how you would go about black-boxing some kind of trivial Windows application. The hypothetical app could be purely the interviewer's brainchild, or the clone of some common utility (<span style="font-style: italic;">e.g.</span> Calculator, Notepad, Paint). Whatever the particulars, let's refer to it as Simple App.<br /><br />Despite the stunning simplicity of Simple App, resist the temptation to begin immediately rattling off the first 100 tests that pop into your head. This is not an exercise for you to come up with as many tests as possible. Yes, of course they want to probe your black-box testing expertise. But above all, <span style="font-style: italic;">they want to know whether your overall approach is methodical.</span><br /><br />Here's how to tackle it. Tell them you have a 3-step approach:<br /><ol><li><span style="font-weight: bold;">You will conduct a smoke test.</span> This has to come first, because you need an understanding of what Simple App basically does in order to write the test plan. Plus, should you encounter any severe test blockers, you can focus your planning effort on the features which actually work.<br /><br />Start with a clean VMware image. Enable <a href="http://blogs.technet.com/askperf/archive/2008/01/08/understanding-crash-dump-files.aspx">system memory dumps</a> and <a href="http://blogs.technet.com/askperf/archive/2007/06/15/capturing-application-crash-dumps.aspx">application crash dumps</a>, then take a snapshot of the pristine OS. Launch <a href="https://technet.microsoft.com/en-us/sysinternals/bb896645.aspx">Process Monitor</a> and install Simple App. Note all the system modifications made by the installer, including files and Registry entries. Take a second VMware snapshot.<br /><br />Launch Simple App. Process Monitor should point out any config files and/or log files. Take an inventory of the app's major features. <span style="font-style: italic;">(At this point the interrogation begins in earnest, with the interviewer answering all the questions you have about Simple App. Take notes.)</span><br /><br />After you have an overview of the feature set, you want to peek under the hood. Launch <a href="http://msdn.microsoft.com/en-us/library/ms235265.aspx">Depends</a> and examine the list of DLLs required by Simple App. You're looking for DLLs to reveal nonobvious functionality. Run <a href="http://technet.microsoft.com/en-us/sysinternals/bb897439.aspx">Strings</a> to find error messages, hard-coded paths, or other useful hints.<br /><br /></li><li><span style="font-weight: bold;">You will write a test plan.</span> This is where to put all your feature-specific tests.<br /><br />No matter how brief, having a concrete plan is necessary for time management and tracking your testing progress.<br /><br />Your test plan should identify any likely risks to Simple App's quality. At the very least, this boils down to answering the question: What could go wrong with this app?<br /><br />Explain that you anticipate having to adjust your plan to changing test conditions, like drilling down as you encounter particularly buggy features. Also at this point, the interviewer often will play around with the precise features of Simple App. Roll with the punches.<br /><br />Without fail, your test plan must <span style="font-style: italic;">always</span> include running under <a href="http://technet.microsoft.com/en-us/library/bb457063.aspx">AppVerifier</a>. <span style="font-style: italic;">Do not omit this.</span><br /><br /></li><li><span style="font-weight: bold;">You will follow the test plan</span>, recording test results and any other notations as you progress. Mark down which tests have passed, failed, or been skipped. This would allow someone else to review or continue your work.<br /></li></ol>If you stick to an approach like this, you will demonstrate that you possess a methodical approach toward testing.Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-62262241752131101512009-04-02T02:02:00.001-07:002009-04-02T04:00:39.980-07:00A Story Involving Steve JobsBack in the late 1990s, when Microsoft and Netscape were waging a bloody war for browser supremacy, my then-employer Macromedia had a problem. We wanted everybody to have the Shockwave browser plug-in, so there would be a vast audience for multimedia content created with Macromedia Director. But a lot of end users were resistant to installing browser plug-ins on their own.<br /><br />This could have been a moot point had Netscape bundled Shockwave with their default installer, but they never agreed to that. So the only remaining avenue for Shockwave ubiquity was to get the plug-in preinstalled with the operating systems.<br /><br />Microsoft eventually agreed to bundle Shockwave with OEM versions of Windows 95, and Apple signed on as well. But despite this cooperation, there were a few problems. Occasionally one vendor or the other would ship an inline rev of their OS and spit out a new CD-ROM without telling us. This was always galling because Shockwave itself received frequent updates, and from Macromedia's perspective it was always a missed opportunity when a CD went out the door with an old version of Shockwave on it.<br /><br />In the fall of 1997, Apple notified us that they were getting ready to ship an update to Mac OS 8. Over the next several weeks, our product managers made attempts to get the latest build of Shockwave included on the CD, but Apple basically ignored us until it was too late. By the time they returned our phone calls, they had already finalized their CD. There would be no changes. And the worst thing? They were including Shockwave 5, even though months prior we had shipped version 6.<br /><br />Apple QA just would not budge. No amount of cajoling, begging, or arm-twisting could get them to crack open the BOM and replace the old Shockwave with the current version. They told us they had declared Gold Master, and they were categorically unwilling to take on the risk of accepting new bits from us.<br /><br />Then, about a week later, something happened.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.flickr.com/photos/mseery/862301657/"><img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 240px; height: 160px;" src="http://farm2.static.flickr.com/1175/862301657_9ebbc2d308_m.jpg" alt="" border="0" /></a>As <a href="http://www.flickr.com/photos/mseery/862301657/">mentioned elsewhere</a>, Steve Jobs arrived at Macromedia for a pow-wow with our executive staff. He and his guys were greeted with a very warm reception at the 3rd floor elevators, and Steve gave a little speech. Then the Apple guys were ushered into a big conference room and everybody went back to their cubicles.<br /><br />When I arrived at my desk, I was suddenly ordered by my boss to drop everything and perform a sanity test on Shockwave 6 with IE Mac. So I downloaded the plug-in and kicked off the automated test suite. A few minutes later, my boss gets a call from the VP of our division. We tell him everything looked good, which was no surprise. Our team had already done a full test pass of Shockwave 6 against Mac IE. The VP hangs up and returns to the meeting with Steve Jobs.<br /><br />A couple of hours later, the phone rings. It's somebody at Apple. All of a sudden, after having stonewalled for weeks, they're <span style="font-style: italic;">downright eager</span> to crack open the precious Mac OS 8 Gold Master to drop in Shockwave 6.<br /><br />Later on, we catch the story from someone who was in the room. Steve Jobs asked what Apple could do for Macromedia, and our VP mentioned that for starters they could bundle Shockwave 6 with their OS 8 CD. Upon hearing this, Jobs reportedly turned to an underling and simply said: <span style="font-style: italic;">"Do it."</span><br /><br />Voila!Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-41310035297905441442009-03-15T11:46:00.000-07:002009-03-15T12:42:02.457-07:00UTF-8 Parser Stress TestingIf your product accepts UTF-8 text as valid input, then Markus Kuhn is your friend. His "UTF-8 decoder capability and stress test" is a textfile containing dozens of malformed UTF-8 byte sequences. This file should be part of your standard acceptance suite. You need to know if your UTF-8 parser fails (for instance, crashes) on malformed input.<br /><br /><a href="http://www.cl.cam.ac.uk/%7Emgk25/ucs/examples/UTF-8-test.txt">http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt</a><br /><br />And if you're weak on the whole concept of Unicode and character encoding in general, Joel Spolsky's primer is a good place to start: <a href="http://www.joelonsoftware.com/articles/Unicode.html">http://www.joelonsoftware.com/articles/Unicode.html</a>Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0tag:blogger.com,1999:blog-4537233927517608417.post-54627203535305021832009-03-03T21:35:00.000-08:002009-03-03T22:51:33.431-08:00Minor FeaturesQuality Assurance involves more than just testing. It means doing whatever is necessary to meet the Dev team's ultimate objective: <span style="font-weight: bold;">shipping quality software on time</span>. True, the QA staff spends a majority of its time testing. But in most organizations, QA is expected to play a customer advocacy role (often moreso than the programming staff). A good software team relies on QA to provide useful feedback on feature design specs, usability testing, beta programs, and so forth.<br /><br />One time I was assigned to test a new feature, which had been originally intended to be fairly minor, for some server-based application. The feature was so minor that Product Management didn't even bother drafting a functional spec, unless you count a couple of sentences in an email. Basically, they wanted the product to save off its raw input data stream into binary files, for possible processing by an external tool to come later (TBD) that our customers were never going to have to see.<br /><br />The programmer who got this assignment took his cue from Product Management and immediately went to work banging out the file storage feature without drafting an Architectural Design doc. Despite the name, this type of document is usually quite brief. It's basically a proposal outlining the anatomy of the essential guts of the feature, in just enough detail for another programmer to review it over a coffee break. It is also relied upon by QA for designing their tests. But that didn't exist in this case.<br /><br />Anyway, the data logging feature shows up in the next build without much advance notice. I have no functional spec and no design doc. So, for the time being, I am left to my own best judgment about how the feature ought to work.<br /><br />The one thing that immediately stands out is the autogenerated filenames. The programmer had reasonably decided that each data file should bear the timestamp of its creation time in the filename. Which makes perfect sense, no arguing with that. The problem is, the programmer decided that the best approach for a timestamp was to use UNIX time. That is, the name of each data file created by our product was the number of seconds elapsed since the UNIX epoch. Hence, a data file created on Friday February 13th 2009 23:31:30 GMT would be given the incomprehensible filename <span style="font-weight: bold;">1234567890.dat</span>.<br /><br />So I filed a bug against the file naming convention. My rationale was that maybe someday our customers might need to browse through a large folder of these files, looking for data from a particular date and time. I suggested that 20090213233130.dat would be a lot easier for a human being to parse in a listing of files than 1234567890.dat, despite the longer name. My bug was deferred as a future enhancement, with the stated rationale being that our customers were never going to see these files and so the obtuse names didn't matter.<br /><br />If only the developer had circulated a design doc, I could have made my recommendation before the code was ever written. Then the whole situation would have been moot.<br /><br />In the end this minor data logging feature, which had been implemented without a feature spec or a design doc, subsequently gained greater prominence. It was relied upon by other internal components of the product and even advertised as a useful feature for our customers. But by then the entire team was stuck with the unhelpful naming convention for those data files, because other code actually relied upon the once-arbitrary file naming scheme.<br /><br />The moral here? There are no minor features, only features that look minor today.Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com1tag:blogger.com,1999:blog-4537233927517608417.post-39118181514299150692009-02-24T15:08:00.000-08:002009-02-24T23:02:59.388-08:00Measuring SuccessYou don't want your customers to encounter serious bugs. Nevertheless, there will always be bugs in the software you ship. You can't find every bug, and you can't even afford to fix all the bugs that you do find. All you can do is work hard to find the most severe bugs quickly and efficiently, so that there are no major customer issues.<br /><br />Thus I can sum up the mission of Quality Assurance in two words:<br /><br /><div style="text-align: center;"><span style="font-weight: bold;">No Surprises</span>.<br /></div><br />Your users are going to find lots of little things, guaranteed. Minor UI glitches, bad help system content, minor incompatibilities with 3rd-party software -- none of that should be surprising. The testing effort is a success if all the defects reported by your customers are either minor or already in the bugbase.<br /><br />Conversely, your testing effort was lacking if your users report serious issues not already in the bugbase. When that happens, you need to take a hard look at your methodology and figure out exactly how those bugs escaped notice.Michael Seeryhttp://www.blogger.com/profile/01737789395806928164noreply@blogger.com0