Jekyll2021-05-16T17:12:37+00:00https://methedrine.org/feed.xmlNotes from a software developerRandom thoughts on software development.About taking short-cuts in software development2015-05-27T19:33:23+00:002015-05-27T19:33:23+00:00https://methedrine.org/software%20development/2015/05/27/about-taking-short-cuts-in-software-development<p>Most people that work in the software development industry - especially in the video games industry - will have read or heard the following quote by <a href="https://en.wikipedia.org/wiki/John_Carmack" title="Wikipedia entry on John Carmack">John Carmack</a> at some point in their career:</p>
<blockquote>
<p>“The cost of adding a feature isn’t just the time it takes to code it. The cost also includes the addition of an obstacle to future expansion. The trick is to pick the features that don’t fight each other.”</p>
</blockquote>
<p>What a lot of people do not realize, however, is that the term “features” is not necessarily referring to end-user functionality. It also refers to the effects your product may have on the end-user’s system as well as your development and operational infrastructure. Every element you add in your UI causes a more complex user experience. Every line of code that gets written needs to be tested. Every web service you add needs to be hosted. Every server you add needs to be monitored.</p>
<p>How does this relate to taking short-cuts in software development? Simply put, there’s a fine line when it comes to the justification of taking a short-cut now, as opposed to waiting for more bandwidth to develop it properly. There is obviously no silver bullet answer to decide when this line is crossed. But here are some questions that can help you in making a decision:</p>
<ol>
<li>What error conditions does the short-cut introduce and how much effort is it to gracefully handle them?</li>
<li>How much effort is it to transition from the short-cut to the fully-fledged experience?</li>
<li>How much effort is it to adjust your development infrastructure to support delivery of this feature?</li>
</ol>
<p>From experience, then, everything that involves out-of-process IO (network communication, database interactions, file IO, etc) and development infrastructure adjustments tends to answer at least one of the questions with “quite some effort”. And that is the moment when your alarm bells should start to ring.</p>Most people that work in the software development industry - especially in the video games industry - will have read or heard the following quote by John Carmack at some point in their career:Stop creating all those source code files!2015-02-04T16:08:54+00:002015-02-04T16:08:54+00:00https://methedrine.org/software%20development/2015/02/04/stop-creating-all-those-source-code-files<p>Apologies, this is going to be a bit of a short rant, though it’s about something that’s been bothering me for quite a while now. Us programmers - we tend to create too many files when we write source code! But why is this bothering me?</p>
<p>Let’s start by looking at some facts. First of all, at execution time the machine does not care about how many files things were split into - it all lives in the same memory space. Secondly, all decent programming languages already offer different ways of structuring your code in logical units (function, classes, namespaces, etc.) in a way that can be automatically validated.</p>
<p>So why do we keep splitting code into different files, in different folders? In my opinion, there are only two valid reasons for splitting code into separate files: size limitations imposed by compiler/interpreter/editor and moving common code into a place where it can be shared so that we don’t require copy and paste. There is a third reason, however, which I think is the main reason why we tend to split code into files; namely that a lot of our tools used to suffer from a lack of dealing with code structure in a proper way. But that is a thing of the past - nowadays editors have features like <a href="http://en.wikipedia.org/wiki/Code_folding" title="Wikipedia - Code Folding">code folding</a>, <a href="http://www.andrewbragdon.com/codebubbles_site.asp" title="Code Bubbles - Rethinking the User Interface Paradigm of Integrated Development Environments">code bubbles</a>, <a href="http://editorconfig.org/">editorconfig</a>, et cetera, so this cannot really be a reason anymore.</p>
<p>“But wait, what is the downside of splitting code into files for other reasons than the first two?” I hear you asking. And here’s my answer: It creates an additional structural level that is ambigious and cannot be automatically validated without writing special tools for it. Suddenly you have to deal with questions along the lines of “should this code be in this file or that file?”, “will this create circular imports?” or “Where do I have to put configuration values again?” - how can this be an acceptable additional headache to anyone who is working on a major (read: not just a simple automation script) project? Every major project already has tons of other, in my opinion more important, aspects to consider - namely those directly required for fulfilling the projects duty. There are even more drawbacks that are introduced - for example, you need to deal with filesystem restrictions (granted these are pretty relaxed nowadays) and coming up with meaningful names - already a hard problem when programming - gets added to yet another layer (“defines.h” or “constants.json” are not helpful). And don’t forget the major culprit of forcing this extra burden on your co-workers.</p>
<p>All this takes energy away from actually solving the problems posed by your project, and thus reduce turn-around times and time-to-market.</p>
<p>Hence: Stop creating all those source code files!</p>Apologies, this is going to be a bit of a short rant, though it’s about something that’s been bothering me for quite a while now. Us programmers - we tend to create too many files when we write source code! But why is this bothering me?Using Google Analytics Measurement Protocol with C++/Qt – A Software Engineering Exercise – Part 52015-01-24T15:26:24+00:002015-01-24T15:26:24+00:00https://methedrine.org/software%20development/2015/01/24/using-google-analytics-measurement-protocol-with-cqt-a-software-engineering-exercise-part-5<p>It’s time for a small, well-overdue post-mortem on this exercise. Overall, I’m both pleased and disappointed with the result of this exercise: Pleased, because the library is usable, and I’ve learned a good deal about applying modern software engineering practices using C++/Qt and open source services. On the other hand, I also feel that it’s not quite fitting as a C++ library, mostly because it does neither use all help the compiler provides, nor is it providing a particularly great API to program against.</p>
<p>One of the interesting aspects in the beginning was to see if this development approach would cost me a lot more in terms of time. And yes, it did - though mostly because I had to familiarize with Coverity and Travis-CI. The actual implementation time was less than 2 working days, including the overhead of setting up the project infrastructure and some moments of taking the API a bit too far. Once again, following the <a href="http://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it" title="You ain't gonna need it">YAGNI</a> principle was quite challenging.</p>
<p>Another point of interest was to see about the product quality, which admittedly is a rather imprecise term. What I’m happy with is that “it works” and “is maintainable” thanks to a set of tests. But I’m unhappy with quite a few things. For starters, <a href="http://jacobian.org/writing/great-documentation/" title="Writing Great Documentation">documentation and examples</a> are non-existent, which is a really good reason for a library not being adopted by anyone. Furthermore, it’s still too easy to make a hard-to-debug mistake by misspelling the Google Analytics parameters. Additionally there’s a lack of validating the parameters for custom metrics and custom dimensions. And then there is also the aspect that the code is not very efficient. All this leaves room for improvements, of which each single one could be considered as “you ain’t gonna need it” at the time of writing.</p>
<p>To summarize, then I am happy that I learned more about setting up a CI loop. The knowledge gained from this exercise was vital in setting up and fine tuning the build infrastructure at my current job. However, I’ve also learned that YAGNI can be an easy excuse for cutting too many corners.</p>It’s time for a small, well-overdue post-mortem on this exercise. Overall, I’m both pleased and disappointed with the result of this exercise: Pleased, because the library is usable, and I’ve learned a good deal about applying modern software engineering practices using C++/Qt and open source services. On the other hand, I also feel that it’s not quite fitting as a C++ library, mostly because it does neither use all help the compiler provides, nor is it providing a particularly great API to program against.Using setuptools with the Microsoft Visual C++ Compiler for Python 2.72014-11-19T00:24:58+00:002014-11-19T00:24:58+00:00https://methedrine.org/software%20development/2014/11/19/using-setuptools-with-the-microsoft-visual-c-compiler-for-python-2-7<p>Just a couple of days ago I faced the problem that I wanted to write a <a href="https://www.python.org/" title="The python homepage">Python 2.7</a> module in C. Why in C, you ask? It had nothing to do with number crunching, or performance optimization. It was merely just to create Python bindings for <a href="https://partner.steamgames.com/home" title="Valve's Steamworks SDK">Valve’s Steamworks SDK</a>. This is strictly speaking not a super difficult task in this day and age, except that Python 2.7 uses a fairly old compiler on Windows that is pretty much unavailable nowadays - Microsoft Visual C++ 2008. Fortunately Microsoft seems to have been made aware of this problem. As I found out after a few Google searches, they are now providing an <a href="https://www.microsoft.com/en-us/download/details.aspx?id=44266" title="Microsoft Visual C++ Compiler for Python 2.7">unsupported package containing the required compiler and libraries</a>. Nice! At least that was my thought until I ran into some issues getting it to work with <a href="https://pypi.python.org/pypi/setuptools" title="Setuptools on the Python Package Index">setuptools</a>. Since the package is not supported by Microsoft, I want to share what I had to do to get it to work. Note that I installed the compiler package for all users on my computer, but the steps should be the same for the “current user only” installation - it’s just a different installation directory after all.</p>
<p>So, what were the problems? Well, first of all, the installed compiler does not add any registry values that setuptools’ heuristics for detecting MSVC could find. Not really a big deal because the heuristics fall back to an environment variable - in this case it should be <code class="language-plaintext highlighter-rouge">VS90COMNTOOLS</code>. However, as the second issue, the compiler package’s setup routine does not create this environment variable. This should also be a trivial thing to add. Except, thirdly, the compiler package has a different installation path and directory layout from the normal Visual Studio distributions. Thus simply copy/pasting the environment variable for a different visual studio version is not enough, it is needs to be slightly adjusted: Instead of pointing towards <code class="language-plaintext highlighter-rouge">[Visual Studio Path]\Common7\Tools\</code> I had to point it to <code class="language-plaintext highlighter-rouge">[Visual Studio Path]\VC\bin</code>. This gets us far enough for setuptools to find the location where it expects the <code class="language-plaintext highlighter-rouge">vcvarsall.bat</code> file. All good, right? No! Because, last but not least, the expected <code class="language-plaintext highlighter-rouge">vcvarsall.bat</code> file does not exist in this distribution. I had to copy one from my <code class="language-plaintext highlighter-rouge">[Visual Studio 2013 Path]\VC\</code> directory to the VC directory of the Visual C++ compiler for python installation folder, and do a manual edit in this copied batch file: The line with <code class="language-plaintext highlighter-rouge">set VisualStudioVersion=12.0</code> needs to be changed so that the version number is 9.0. And that finally got me going.</p>
<p>Note: there is a <code class="language-plaintext highlighter-rouge">vcvarsall.bat</code> file in the root directory of the compiler installation. Copying this into the VC subfolder will not work unless you modify all the paths in there. It’s probably an option as well, but requires more changes than just adjusting a version number.</p>Just a couple of days ago I faced the problem that I wanted to write a Python 2.7 module in C. Why in C, you ask? It had nothing to do with number crunching, or performance optimization. It was merely just to create Python bindings for Valve’s Steamworks SDK. This is strictly speaking not a super difficult task in this day and age, except that Python 2.7 uses a fairly old compiler on Windows that is pretty much unavailable nowadays - Microsoft Visual C++ 2008. Fortunately Microsoft seems to have been made aware of this problem. As I found out after a few Google searches, they are now providing an unsupported package containing the required compiler and libraries. Nice! At least that was my thought until I ran into some issues getting it to work with setuptools. Since the package is not supported by Microsoft, I want to share what I had to do to get it to work. Note that I installed the compiler package for all users on my computer, but the steps should be the same for the “current user only” installation - it’s just a different installation directory after all.Hello D World2014-08-13T21:35:12+00:002014-08-13T21:35:12+00:00https://methedrine.org/software%20development/2014/08/13/hello-d-world<p>A famous saying in the software development world is that “programmers should learn a new language every year”. I have not really done that in quite a while, for a multitude of reasons: First of all, I don’t think that one really learns a programming language within a year. Yes, it is easy to get started and be able to read and write simple programs, but it usually takes quite a bit longer until one really absorbs the finer details of a language. Secondly, there is hardly any use in knowing more than 4 or 5 major languages and I’m doing pretty well with fluently writing C/C++ and Python code (though, of course, I can read quite a few more programming languages like, for example, Java and good old ASM). That being said, there is always a new technology rolling around and a lot of times it is worthwhile to take at least a brief look at these just in case major parts of the industry start jumping on it (remember: if you are not part of the steamroller, you are likely part of the road). And sometimes you really just want to see if there is a replacement for the horribly broken stuff that you have to deal with on a daily base at work (yes, I am looking at you, C++).</p>
<p>One specific language that has been popping up on my radar every now and then over the last 2-3 years was <a href="https://dlang.org" title="Home - The D Programming Language">the D programming language</a>. It claims to be a systems programming language and to have the same efficiency as C/C++ while coming with all the bells and whistles that “modern” languages bring along. Furthermore I’ve been a big fan of <a href="https://en.wikipedia.org/wiki/Andrei_Alexandrescu" title="Wikipedia on Andrei Alexandrescu">Andrei Alexandrescu</a> ever since I read his book “Modern C++ Design”, and since he has a major involvement in the development of D and happens to be the author of “The D Programming Language” book, it is more than fair to say that it is time to finally sit down and give this language a serious chance.</p>
<p>As with every programming language, you first need a compiler (or interpreter) that will take care of translating source code into instructions the computer understands. To my surprise <a href="https://dlang.org/download.html" title="D Compiler Downloads">there are already different D compilers available</a>: GDC (based on GCC), LDC (based on LLVM) and DMD (the reference implementation). For the purpose of learning I’ve decided to go with DMD.</p>
<p>Once I was done installing the compiler I fired up a command line window and navigated to the samples directory. A quick glance at the file listing reveals that there is a file called <code class="language-plaintext highlighter-rouge">hello.d</code>, and after opening it in VIM it turns out to be the traditional “Hello World” program, as expected:</p>
<div class="language-d highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="n">std</span><span class="p">.</span><span class="n">stdio</span><span class="p">;</span>
<span class="kt">void</span> <span class="n">main</span><span class="p">(</span><span class="nb">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">writeln</span><span class="p">(</span><span class="s">"hello world"</span><span class="p">);</span>
<span class="n">writefln</span><span class="p">(</span><span class="s">"args.length = %d"</span><span class="p">,</span> <span class="n">args</span><span class="p">.</span><span class="n">length</span><span class="p">);</span>
<span class="k">foreach</span> <span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">arg</span><span class="p">;</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">writefln</span><span class="p">(</span><span class="s">"args[%d] = '%s'"</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">arg</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If you now say “Hang on a second, that looks a lot like a mix of Python and C!” then I’m fully in agreement, as that was pretty much my first response as well. To begin with, the first line is an import statement like we know it from Python, except it’s referencing something that is named similar to the C/C++ standard libraries (C++’s standard library uses a namespace called <code class="language-plaintext highlighter-rouge">std</code>, while C has a standard library file <code class="language-plaintext highlighter-rouge">stdio</code>).</p>
<p>Next up there is a function declaration that looks very similar to the main function as we know it from C - except it only takes one argument <code class="language-plaintext highlighter-rouge">args</code> (an array of strings) as opposed to the classical two arguments <code class="language-plaintext highlighter-rouge">argc</code> (an integer specifying the number of passed arguments) and <code class="language-plaintext highlighter-rouge">argv</code> (a pointer to char* containing a list of <code class="language-plaintext highlighter-rouge">argc</code> arguments). A quick peek at the contents of the function (and a swift look at the language documentation) reveal that <code class="language-plaintext highlighter-rouge">argc</code> is obsolete because arrays are a built-in type in D - very nice, even if it’s not particularly new. Then there’s the <code class="language-plaintext highlighter-rouge">writefln()</code> function which appears to be very similar to C’s <code class="language-plaintext highlighter-rouge">printf()</code> / Python’s <code class="language-plaintext highlighter-rouge">print()</code>. And last but not least we see another interesting construct in this example: The <a href="https://dlang.org/statement.html#ForeachStatement">foreach</a> loop. Now, this kind of loop is nothing new either (for example it exists in Python as <code class="language-plaintext highlighter-rouge">for x in sequence</code>). However, in D it comes with a special syntax for arrays where you can declare two variables instead of one, meaning that the first variable will be the index (or key in the case of an associative array) of the element you are iterating over and the second variable the element. Another big convenience right away, as it obsoletes managing a separate counter variable which could be a potential source of errors when facing a more complicated loop logic. And remember: strings are just an array of character…</p>
<p>But enough about the code for now. Let’s go and get it to run. There are shell- / batch-scripts which compile all examples, but I am naive and only want the hello world example. Thus I just type <code class="language-plaintext highlighter-rouge">dmd hello.d</code> into the terminal and see what happens… interesting, no error and the CPU seems to be busy. But then nothing happened apparently, I just see a new line in the terminal to enter the next command. A new directory listing reveals that there is now an executable called <code class="language-plaintext highlighter-rouge">hello.exe</code>. Executing that works just fine and prints out “hello world” among the length and contents of the argument list the main function received. A quick look at dmd –help then also reveals that I wouldn’t have had to compile the code: invoking dmd with the -run argument would have simply executed the D code like a script. This is another surprise as that means that one could theoretically use D to write small utility scripts. And -run is not the only fascinating command line switch for dmd: it also contains the -unittest option to compile in unittests (which are built into the language) and a -D switch which seems to generate documentation from the source code (yup, documentation is also built into the language). Well then, on to write a first small piece of software in D myself, but more about that in the next post - right now it’s time to enjoy another glorious and beautiful Icelandic sunset.</p>
<p>PS: I know that I owe you the final part of the software engineering exercise article series. It is sitting here as a draft but I’m not quite happy with its wording just yet, so it will take a bit longer until it is done.</p>A famous saying in the software development world is that “programmers should learn a new language every year”. I have not really done that in quite a while, for a multitude of reasons: First of all, I don’t think that one really learns a programming language within a year. Yes, it is easy to get started and be able to read and write simple programs, but it usually takes quite a bit longer until one really absorbs the finer details of a language. Secondly, there is hardly any use in knowing more than 4 or 5 major languages and I’m doing pretty well with fluently writing C/C++ and Python code (though, of course, I can read quite a few more programming languages like, for example, Java and good old ASM). That being said, there is always a new technology rolling around and a lot of times it is worthwhile to take at least a brief look at these just in case major parts of the industry start jumping on it (remember: if you are not part of the steamroller, you are likely part of the road). And sometimes you really just want to see if there is a replacement for the horribly broken stuff that you have to deal with on a daily base at work (yes, I am looking at you, C++).Using Google Analytics Measurement Protocol with C++/Qt – A Software Engineering Exercise – Part 42014-04-04T09:32:11+00:002014-04-04T09:32:11+00:00https://methedrine.org/software%20development/2014/04/04/using-google-analytics-measurement-protocol-with-cqt-a-software-engineering-exercise-part-4<p>OK, it’s been a couple of days since the last post. This is what happens when you have to prioritize some other things - like brewing beer - in between. But now we’re back on track with our small library, and this time we focus on hit parameter validation.</p>
<p>There are some different ways of attempting parameter validation, and all of them have their own pros and cons. Since the tracker already takes care of a minimum set of required parameters, then the first idea is to do something along the lines of this:</p>
<ul>
<li>Determine the hit type - it’s a required parameter</li>
<li>Check if all required parameters for this hit type exist</li>
<li>For all specified parameters: Check if their name is known, and if so, if their value matches expected values.</li>
</ul>
<p>This would do the job, and provide a great first iteration target. And even though it imposes run-time overhead then we really shouldn’t worry about it <em>yet</em>. In fact, since QUrlQuery does not use a QHash or QMap for its values then the first step is to convert our list of string pairs into a more suitable data structure. Once we are done converting we can easily do key look-ups as necessary:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ...</span>
<span class="n">QHash</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span> <span class="n">params</span><span class="p">;</span>
<span class="n">QListIterator</span><span class="o"><</span><span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">>></span> <span class="n">iter</span><span class="p">(</span> <span class="n">parameters</span> <span class="p">);</span>
<span class="k">while</span> <span class="p">(</span> <span class="n">iter</span><span class="p">.</span><span class="n">hasNext</span><span class="p">()</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">param</span> <span class="o">=</span> <span class="n">iter</span><span class="p">.</span><span class="n">next</span><span class="p">();</span>
<span class="n">params</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span> <span class="n">param</span><span class="p">.</span><span class="n">first</span><span class="p">,</span> <span class="n">param</span><span class="p">.</span><span class="n">second</span> <span class="p">);</span>
<span class="p">}</span>
<span class="c1">// ...</span>
</code></pre></div></div>
<p>Now that that’s done we can go and start with writing some tests first, just to make sure that whatever we come up with actually does what we want.</p>
<p>Given the protocol then I am sure that the following tests should be adequate:</p>
<ul>
<li>Check that a valid hit type was specified.</li>
<li>For each of the hit types with required parameters, detect if they are missing a required parameter.</li>
<li>For each parameter value type, detect if it can be detected correctly, e.g. cannot pass text to something that expects an int.</li>
</ul>
<p>Or, in code:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TEST</span><span class="p">(</span><span class="n">Validation</span><span class="p">,</span> <span class="n">hitTypeTests</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Tracker</span><span class="o">::</span><span class="n">ParameterList</span> <span class="n">params</span><span class="p">;</span>
<span class="c1">// 1. hit type is required</span>
<span class="n">EXPECT_FALSE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="c1">// 2. Must be one of 'pageview', 'appview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'.</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"t"</span><span class="p">,</span> <span class="s">"foo"</span> <span class="p">);</span>
<span class="n">EXPECT_FALSE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="c1">// 3. Make sure we succeed on correct parameter</span>
<span class="n">params</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"t"</span><span class="p">,</span> <span class="s">"pageview"</span> <span class="p">);</span>
<span class="n">EXPECT_TRUE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>
<span class="n">TEST</span><span class="p">(</span><span class="n">Validation</span><span class="p">,</span> <span class="n">requiredParameterTests</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Tracker</span><span class="o">::</span><span class="n">ParameterList</span> <span class="n">params</span><span class="p">;</span>
<span class="c1">// 1. check for a hit type that has no required parameters</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"t"</span><span class="p">,</span> <span class="s">"pageview"</span> <span class="p">);</span>
<span class="n">EXPECT_TRUE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="c1">// 2. detect parameters are missing</span>
<span class="n">params</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"t"</span><span class="p">,</span> <span class="s">"item"</span> <span class="p">);</span>
<span class="n">EXPECT_FALSE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>
<span class="n">TEST</span><span class="p">(</span><span class="n">Validation</span><span class="p">,</span> <span class="n">correctParameterTypeTests</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Tracker</span><span class="o">::</span><span class="n">ParameterList</span> <span class="n">params</span><span class="p">,</span> <span class="n">baseParams</span><span class="p">;</span>
<span class="n">baseParams</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"t"</span><span class="p">,</span> <span class="s">"event"</span> <span class="p">);</span>
<span class="c1">// 1. Boolean, valid</span>
<span class="n">params</span> <span class="o">=</span> <span class="n">baseParams</span><span class="p">;</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"aip"</span><span class="p">,</span> <span class="s">"0"</span> <span class="p">);</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"je"</span><span class="p">,</span> <span class="s">"1"</span> <span class="p">);</span>
<span class="n">EXPECT_TRUE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="c1">// 2. Boolean, invalid</span>
<span class="n">params</span> <span class="o">=</span> <span class="n">baseParams</span><span class="p">;</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"aip"</span><span class="p">,</span> <span class="s">"foo"</span> <span class="p">);</span>
<span class="n">EXPECT_FALSE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="c1">// 3. Currency, valid</span>
<span class="n">params</span> <span class="o">=</span> <span class="n">baseParams</span><span class="p">;</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"tr"</span><span class="p">,</span> <span class="s">"-55.00"</span> <span class="p">);</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"ts"</span><span class="p">,</span> <span class="s">"1000.000001"</span> <span class="p">);</span>
<span class="n">EXPECT_TRUE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="c1">// 4. Currency, invalid</span>
<span class="n">params</span> <span class="o">=</span> <span class="n">baseParams</span><span class="p">;</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"tt"</span><span class="p">,</span> <span class="s">"foo"</span> <span class="p">);</span>
<span class="n">EXPECT_FALSE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="c1">// 5. Integer, valid</span>
<span class="n">params</span> <span class="o">=</span> <span class="n">baseParams</span><span class="p">;</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"utt"</span><span class="p">,</span> <span class="s">"-1234567890"</span> <span class="p">);</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"iq"</span><span class="p">,</span> <span class="s">"9876543210"</span> <span class="p">);</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"ev"</span><span class="p">,</span> <span class="s">"0"</span> <span class="p">);</span>
<span class="n">EXPECT_TRUE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="c1">// 6. Integer, invalid</span>
<span class="n">params</span> <span class="o">=</span> <span class="n">baseParams</span><span class="p">;</span>
<span class="n">params</span> <span class="o"><<</span> <span class="n">QPair</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QString</span><span class="o">></span><span class="p">(</span> <span class="s">"plt"</span><span class="p">,</span> <span class="s">"foo"</span> <span class="p">);</span>
<span class="n">EXPECT_FALSE</span><span class="p">(</span> <span class="n">isValidHit</span><span class="p">(</span> <span class="n">params</span> <span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Great! Now it should be fairly trivial to implement a validation routine that satisfies all our tests. First of all, to ensure that a valid hit type is specified we simply need to find the parameter with key “t” and see if its value is in the set of hit types (“pageview”, “event”, “transaction”, etc). This can be achieved with a simple look-up table (or list, in this case):</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ... construct a QHash<QString, QString> params</span>
<span class="n">QList</span><span class="o"><</span><span class="n">qstring</span><span class="o">></span> <span class="n">validHitTypes</span><span class="p">;</span>
<span class="n">validHitTypes</span> <span class="o"><<</span> <span class="s">"pageview"</span> <span class="o"><<</span> <span class="s">"appview"</span> <span class="o"><<</span> <span class="s">"event"</span> <span class="o"><<</span> <span class="s">"transaction"</span><span class="p">;</span>
<span class="n">validHitTypes</span> <span class="o"><<</span> <span class="s">"item"</span> <span class="o"><<</span> <span class="s">"social"</span> <span class="o"><<</span> <span class="s">"exception"</span> <span class="o"><<</span> <span class="s">"timing"</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">foundHitType</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">hitIter</span> <span class="o">=</span> <span class="n">params</span><span class="p">.</span><span class="n">find</span><span class="p">(</span> <span class="s">"t"</span> <span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">hitIter</span> <span class="o">!=</span> <span class="n">params</span><span class="p">.</span><span class="n">end</span><span class="p">()</span> <span class="o">&&</span> <span class="n">validHitTypes</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span> <span class="n">hitIter</span><span class="p">.</span><span class="n">value</span><span class="p">()</span> <span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">foundHitType</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="c1">// ... more validation</span>
<span class="p">}</span>
<span class="c1">// ... etc</span>
</code></pre></div></div>
<p>And now that the first test is passing we can apply similar functionality for required parameters. Let’s do this after we found the hit type, because we really only need to do it at that point:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ... another lookup "table"</span>
<span class="n">QMap</span><span class="o"><</span><span class="n">QString</span><span class="p">,</span> <span class="n">QStringList</span><span class="o">></span> <span class="n">requiredPerHitType</span><span class="p">;</span>
<span class="n">requiredPerHitType</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span> <span class="s">"transaction"</span><span class="p">,</span> <span class="n">QStringList</span><span class="p">()</span> <span class="o"><<</span> <span class="s">"ti"</span> <span class="p">);</span>
<span class="n">requiredPerHitType</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span> <span class="s">"item"</span><span class="p">,</span> <span class="n">QStringList</span><span class="p">()</span> <span class="o"><<</span> <span class="s">"ti"</span> <span class="o"><<</span> <span class="s">"in"</span> <span class="p">);</span>
<span class="n">requiredPerHitType</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span> <span class="s">"social"</span><span class="p">,</span> <span class="n">QStringList</span><span class="p">()</span> <span class="o"><<</span> <span class="s">"sn"</span> <span class="o"><<</span> <span class="s">"sa"</span> <span class="o"><<</span> <span class="s">"st"</span> <span class="p">);</span>
<span class="c1">// ...</span>
<span class="kt">bool</span> <span class="n">hasAllRequiredParameters</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">foundHitType</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">hitIter</span> <span class="o">=</span> <span class="n">params</span><span class="p">.</span><span class="n">find</span><span class="p">(</span> <span class="s">"t"</span> <span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">hitIter</span> <span class="o">!=</span> <span class="n">params</span><span class="p">.</span><span class="n">end</span><span class="p">()</span> <span class="o">&&</span> <span class="n">validHitTypes</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span> <span class="n">hitIter</span><span class="p">.</span><span class="n">value</span><span class="p">()</span> <span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">foundHitType</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">requiredIter</span> <span class="o">=</span> <span class="n">requiredPerHitType</span><span class="p">.</span><span class="n">find</span><span class="p">(</span> <span class="n">hitIter</span><span class="p">.</span><span class="n">value</span><span class="p">()</span> <span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">requiredIter</span> <span class="o">!=</span> <span class="n">requiredPerHitType</span><span class="p">.</span><span class="n">end</span><span class="p">()</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">QListIterator</span><span class="o"><</span><span class="n">qstring</span><span class="o">></span> <span class="n">iter</span><span class="p">(</span> <span class="n">requiredIter</span><span class="p">.</span><span class="n">value</span><span class="p">()</span> <span class="p">);</span>
<span class="k">while</span> <span class="p">(</span> <span class="n">iter</span><span class="p">.</span><span class="n">hasNext</span><span class="p">()</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">value</span> <span class="o">=</span> <span class="n">iter</span><span class="p">.</span><span class="n">next</span><span class="p">();</span>
<span class="n">hasAllRequiredParameters</span> <span class="o">=</span> <span class="n">hasAllRequiredParameters</span> <span class="o">&&</span> <span class="n">params</span><span class="p">.</span><span class="n">keys</span><span class="p">().</span><span class="n">contains</span><span class="p">(</span> <span class="n">value</span> <span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// ...</span>
</code></pre></div></div>
<p>And thus the second set of tests passes as well. Did I already mention that look-up tables are a great thing? Well, now I did. So we are now left with validating the type of the actual parameters. There really is not much to do for the text type because Google more or less accepts everything that’s correctly URL-encoded, as long as it’s shorter than the allowed maximum size. Boolean values are also simple to validate because they must be either “0” (=false) or “1” (=true). Leaving us with Integer, which is a signed 64-bit integer value, and currency. Qt makes validating the 64 bit integer really easy, we simply convert the textual value to a numeric value and back again before we compare both:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">boolean</span> <span class="nf">isValidInteger</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span><span class="o">&</span> <span class="n">value</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">(</span> <span class="n">value</span> <span class="o">==</span> <span class="n">QString</span><span class="o">::</span><span class="n">number</span><span class="p">(</span> <span class="n">value</span><span class="p">.</span><span class="n">toLongLong</span><span class="p">()</span> <span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Validating the currency type is a bit more weird and slightly unclear. Google’s documentation says:</p>
<blockquote>
<p>Used to represent the total value of a currency. A decimal point is used as a delimiter between the whole and fractional portion of the currency. The precision is up to 6 decimal places. […] Once the value is sent to Google Analytics, all text is removed up until the first digit, the - character or the . (decimal) character</p>
</blockquote>
<p>Now, some people, when confronted with a problem instantly go and say: “I know, I can use regular expressions!”. And while they end up with 2 problems most of the time, then it is a completely feasible solution here. As far as I am aware then this is a suitable, non-escaped regular expression, e.g. you’ll have to escape the backslashes if you use it in your C++ code:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.*-?\d*\.\d{2,6}
</code></pre></div></div>
<p>In more readable terms, the expression accepts whatever text is in front of a potential sign, then expects all digits up until it encounters a decimal point, at which point (sic) it then expects another 2 to 6 decimal digits.</p>
<p>So, now that we have basic validation running (except for maximum length of single parameters), we can declare the library as ready to use. In the next part I’m going to reflect a little bit on the experiences gained during the development of this utility, and give a little outlook on where I see room for potential improvements (and there is some room for that) that would make this library better on a code quality level.</p>OK, it’s been a couple of days since the last post. This is what happens when you have to prioritize some other things - like brewing beer - in between. But now we’re back on track with our small library, and this time we focus on hit parameter validation. There are some different ways of attempting parameter validation, and all of them have their own pros and cons. Since the tracker already takes care of a minimum set of required parameters, then the first idea is to do something along the lines of this: Determine the hit type - it’s a required parameter Check if all required parameters for this hit type exist For all specified parameters: Check if their name is known, and if so, if their value matches expected values. This would do the job, and provide a great first iteration target. And even though it imposes run-time overhead then we really shouldn’t worry about it yet. In fact, since QUrlQuery does not use a QHash or QMap for its values then the first step is to convert our list of string pairs into a more suitable data structure. Once we are done converting we can easily do key look-ups as necessary: // ... QHash<QString, QString> params; QListIterator<QPair<QString, QString>> iter( parameters ); while ( iter.hasNext() ) { auto param = iter.next(); params.insert( param.first, param.second ); } // ... Now that that’s done we can go and start with writing some tests first, just to make sure that whatever we come up with actually does what we want. Given the protocol then I am sure that the following tests should be adequate: Check that a valid hit type was specified. For each of the hit types with required parameters, detect if they are missing a required parameter. For each parameter value type, detect if it can be detected correctly, e.g. cannot pass text to something that expects an int. Or, in code: TEST(Validation, hitTypeTests) { Tracker::ParameterList params; // 1. hit type is required EXPECT_FALSE( isValidHit( params ) ); // 2. Must be one of 'pageview', 'appview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. params << QPair<QString, QString>( "t", "foo" ); EXPECT_FALSE( isValidHit( params ) ); // 3. Make sure we succeed on correct parameter params.clear(); params << QPair<QString, QString>( "t", "pageview" ); EXPECT_TRUE( isValidHit( params ) ); } TEST(Validation, requiredParameterTests) { Tracker::ParameterList params; // 1. check for a hit type that has no required parameters params << QPair<QString, QString>( "t", "pageview" ); EXPECT_TRUE( isValidHit( params ) ); // 2. detect parameters are missing params.clear(); params << QPair<QString, QString>( "t", "item" ); EXPECT_FALSE( isValidHit( params ) ); } TEST(Validation, correctParameterTypeTests) { Tracker::ParameterList params, baseParams; baseParams << QPair<QString, QString>( "t", "event" ); // 1. Boolean, valid params = baseParams; params << QPair<QString, QString>( "aip", "0" ); params << QPair<QString, QString>( "je", "1" ); EXPECT_TRUE( isValidHit( params ) ); // 2. Boolean, invalid params = baseParams; params << QPair<QString, QString>( "aip", "foo" ); EXPECT_FALSE( isValidHit( params ) ); // 3. Currency, valid params = baseParams; params << QPair<QString, QString>( "tr", "-55.00" ); params << QPair<QString, QString>( "ts", "1000.000001" ); EXPECT_TRUE( isValidHit( params ) ); // 4. Currency, invalid params = baseParams; params << QPair<QString, QString>( "tt", "foo" ); EXPECT_FALSE( isValidHit( params ) ); // 5. Integer, valid params = baseParams; params << QPair<QString, QString>( "utt", "-1234567890" ); params << QPair<QString, QString>( "iq", "9876543210" ); params << QPair<QString, QString>( "ev", "0" ); EXPECT_TRUE( isValidHit( params ) ); // 6. Integer, invalid params = baseParams; params << QPair<QString, QString>( "plt", "foo" ); EXPECT_FALSE( isValidHit( params ) ); } Great! Now it should be fairly trivial to implement a validation routine that satisfies all our tests. First of all, to ensure that a valid hit type is specified we simply need to find the parameter with key “t” and see if its value is in the set of hit types (“pageview”, “event”, “transaction”, etc). This can be achieved with a simple look-up table (or list, in this case): // ... construct a QHash<QString, QString> params QList<qstring> validHitTypes; validHitTypes << "pageview" << "appview" << "event" << "transaction"; validHitTypes << "item" << "social" << "exception" << "timing"; bool foundHitType = false; auto hitIter = params.find( "t" ); if ( hitIter != params.end() && validHitTypes.contains( hitIter.value() ) ) { foundHitType = true; // ... more validation } // ... etc And now that the first test is passing we can apply similar functionality for required parameters. Let’s do this after we found the hit type, because we really only need to do it at that point: // ... another lookup "table" QMap<QString, QStringList> requiredPerHitType; requiredPerHitType.insert( "transaction", QStringList() << "ti" ); requiredPerHitType.insert( "item", QStringList() << "ti" << "in" ); requiredPerHitType.insert( "social", QStringList() << "sn" << "sa" << "st" ); // ... bool hasAllRequiredParameters = true; bool foundHitType = false; auto hitIter = params.find( "t" ); if ( hitIter != params.end() && validHitTypes.contains( hitIter.value() ) ) { foundHitType = true; auto requiredIter = requiredPerHitType.find( hitIter.value() ); if ( requiredIter != requiredPerHitType.end() ) { QListIterator<qstring> iter( requiredIter.value() ); while ( iter.hasNext() ) { auto value = iter.next(); hasAllRequiredParameters = hasAllRequiredParameters && params.keys().contains( value ); } } } // ... And thus the second set of tests passes as well. Did I already mention that look-up tables are a great thing? Well, now I did. So we are now left with validating the type of the actual parameters. There really is not much to do for the text type because Google more or less accepts everything that’s correctly URL-encoded, as long as it’s shorter than the allowed maximum size. Boolean values are also simple to validate because they must be either “0” (=false) or “1” (=true). Leaving us with Integer, which is a signed 64-bit integer value, and currency. Qt makes validating the 64 bit integer really easy, we simply convert the textual value to a numeric value and back again before we compare both: boolean isValidInteger(const QString& value) { return ( value == QString::number( value.toLongLong() ) ); } Validating the currency type is a bit more weird and slightly unclear. Google’s documentation says: Used to represent the total value of a currency. A decimal point is used as a delimiter between the whole and fractional portion of the currency. The precision is up to 6 decimal places. […] Once the value is sent to Google Analytics, all text is removed up until the first digit, the - character or the . (decimal) character Now, some people, when confronted with a problem instantly go and say: “I know, I can use regular expressions!”. And while they end up with 2 problems most of the time, then it is a completely feasible solution here. As far as I am aware then this is a suitable, non-escaped regular expression, e.g. you’ll have to escape the backslashes if you use it in your C++ code: .*-?\d*\.\d{2,6} In more readable terms, the expression accepts whatever text is in front of a potential sign, then expects all digits up until it encounters a decimal point, at which point (sic) it then expects another 2 to 6 decimal digits. So, now that we have basic validation running (except for maximum length of single parameters), we can declare the library as ready to use. In the next part I’m going to reflect a little bit on the experiences gained during the development of this utility, and give a little outlook on where I see room for potential improvements (and there is some room for that) that would make this library better on a code quality level.Using Google Analytics Measurement Protocol with C++/Qt – A Software Engineering Exercise – Part 32014-03-19T21:46:24+00:002014-03-19T21:46:24+00:00https://methedrine.org/software%20development/2014/03/19/using-google-analytics-measurement-protocol-with-cqt-a-software-engineering-exercise-part-3<p>So far in this series I have only talked about the rudimentary infrastructure I used for setting up a continuous integration loop and a very specific problem to resolve before I could start test driven development. In this part, however, I want to share some more insight about the actual programming process so far. Note that while I will provide code samples, then I am going to omit accompanying test cases, mostly because I am not going to use.</p>
<p>When you look at the protocol’s documentation you’ll quickly realize that it is nothing more than a url-encoded key value list that is being submitted to a specific webserver. This means we could simply use <code class="language-plaintext highlighter-rouge">QNetworkRequest</code> instances everywhere we want to track something, for example like this:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">QNetworkAccessManager</span> <span class="n">g_nam</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">QNetworkRequest</span> <span class="n">req</span><span class="p">;</span>
<span class="n">req</span><span class="p">.</span><span class="n">setUrl</span><span class="p">(</span><span class="s">"http://www.google-analytics.com/collect"</span><span class="p">);</span>
<span class="n">QString</span> <span class="n">trackerQuery</span><span class="p">(</span><span class="s">"v=1&cid=555&tid=UA-12345-678&t=event&ec=test&ev=1&ea=event1"</span><span class="p">);</span>
<span class="n">QByteArray</span> <span class="n">data</span> <span class="o">=</span> <span class="n">trackerQuery</span><span class="p">.</span><span class="n">toLatin1</span><span class="p">();</span>
<span class="n">g_nam</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="kt">void</span> <span class="nf">bar</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">QNetworkRequest</span> <span class="n">req</span><span class="p">;</span>
<span class="n">req</span><span class="p">.</span><span class="n">setUrl</span><span class="p">(</span><span class="s">"http://www.google-analytics.com/collect"</span><span class="p">);</span>
<span class="n">QString</span> <span class="n">trackerQuery</span><span class="p">(</span><span class="s">"v=1&cid=555&tid=UA-12345-679&t=event&ec=test&ea=event2"</span><span class="p">);</span>
<span class="n">QByteArray</span> <span class="n">data</span> <span class="o">=</span> <span class="n">trackerQuery</span><span class="p">.</span><span class="n">toLatin1</span><span class="p">();</span>
<span class="n">g_nam</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now, as one can see there is a lot of duplication in this. The first obvious step here would be to take the <code class="language-plaintext highlighter-rouge">trackerQuery</code> string out of it:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">QNetworkAccessManager</span> <span class="n">g_nam</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">track</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span><span class="o">&</span> <span class="n">query</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">QNetworkRequest</span> <span class="n">req</span><span class="p">;</span>
<span class="n">req</span><span class="p">.</span><span class="n">setUrl</span><span class="p">(</span><span class="s">"http://www.google-analytics.com/collect"</span><span class="p">);</span>
<span class="n">QByteArray</span> <span class="n">data</span> <span class="o">=</span> <span class="n">query</span><span class="p">.</span><span class="n">toLatin1</span><span class="p">();</span>
<span class="n">g_nam</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">foo</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">track</span><span class="p">(</span><span class="n">QString</span><span class="p">(</span><span class="s">"v=1&cid=555&tid=UA-12345-678&t=event&ec=test&ev=1&ea=event1"</span><span class="p">));</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">bar</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">track</span><span class="p">(</span><span class="n">QString</span><span class="p">(</span><span class="s">"v=1&cid=555&tid=UA-12345-679&t=event&ec=test&ea=event2"</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This already avoids a lot of the code duplication, but still leaves us with a lot to be desired. For example, the <code class="language-plaintext highlighter-rouge">QNetworkAccessManager</code> instance is currently global, which means there is some shared state between the <code class="language-plaintext highlighter-rouge">track()</code> method and the rest of your application. Additionally we don’t even know if the request was sent at all because we are not listening to any of <code class="language-plaintext highlighter-rouge">QNetworkAccessManager</code>’s signals that would provide us with that information. Thirdly, the string we are passing into <code class="language-plaintext highlighter-rouge">track()</code> contains a lot of redundant inormation and there is no validation of whether it actually is valid according to the API’s or our requirements - for instance it is difficult to spot the typo that I made in <code class="language-plaintext highlighter-rouge">bar()</code>’s tracker ID. This particular issue could turn into a very difficult to debug problem that you probably won’t notice until the person who asked for that statistic to be collected comes asking why it does not show up with anything.</p>
<p>So, let’s tackle these problems one by one. Or in this case, let’s just fix the first two problems in one go, really. For that we wrap the track method in a <code class="language-plaintext highlighter-rouge">QObject</code>-derived class, which also allows us to connect to the <code class="language-plaintext highlighter-rouge">QNetworkAccessManager</code>’s signals.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Tracker</span> <span class="o">:</span> <span class="k">public</span> <span class="n">QObject</span>
<span class="p">{</span>
<span class="n">Q_OBJECT</span>
<span class="nl">public:</span>
<span class="k">explicit</span> <span class="n">Tracker</span><span class="p">(</span> <span class="n">QObject</span><span class="o">*</span> <span class="n">parent</span> <span class="o">=</span> <span class="nb">nullptr</span> <span class="p">)</span> <span class="o">:</span> <span class="n">QObject</span><span class="p">(</span><span class="n">parent</span><span class="p">),</span> <span class="n">m_nam</span><span class="p">(</span><span class="k">new</span> <span class="n">QNetworkAccessManager</span><span class="p">(</span><span class="k">this</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">connect</span><span class="p">(</span><span class="n">m_nam</span><span class="p">,</span> <span class="n">SIGNAL</span><span class="p">(</span><span class="n">finished</span><span class="p">(</span><span class="n">QNetworkReply</span><span class="o">*</span><span class="p">)),</span> <span class="k">this</span><span class="p">,</span> <span class="n">SLOT</span><span class="p">(</span><span class="n">onFinished</span><span class="p">(</span><span class="n">QNetworkReply</span><span class="o">*</span><span class="p">)));</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">track</span><span class="p">(</span> <span class="k">const</span> <span class="n">QString</span><span class="o">&</span> <span class="n">data</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span> <span class="n">m_nam</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">qCritical</span><span class="p">(</span><span class="s">"No network manager specified!"</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">QNetworkRequest</span> <span class="n">req</span><span class="p">;</span>
<span class="n">req</span><span class="p">.</span><span class="n">setUrl</span><span class="p">(</span><span class="s">"http://www.google-analytics.com/collect"</span><span class="p">);</span>
<span class="n">QByteArray</span> <span class="n">data</span> <span class="o">=</span> <span class="n">query</span><span class="p">.</span><span class="n">toLatin1</span><span class="p">();</span>
<span class="n">m_nam</span><span class="o">-></span><span class="n">post</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">setNetworkAccessManager</span><span class="p">(</span><span class="n">QNetworkAccessManager</span><span class="o">*</span> <span class="n">nam</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">m_nam</span> <span class="o">&&</span> <span class="n">m_nam</span><span class="o">-></span><span class="n">parent</span><span class="p">()</span> <span class="o">==</span> <span class="k">this</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">delete</span> <span class="n">m_nam</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">m_nam</span> <span class="o">=</span> <span class="n">nam</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="n">slots</span><span class="o">:</span>
<span class="kt">void</span> <span class="n">onFinished</span><span class="p">(</span><span class="n">QNetworkReply</span><span class="o">*</span> <span class="n">reply</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">reply</span><span class="o">-></span><span class="n">deleteLater</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">reply</span><span class="o">-></span><span class="n">error</span><span class="p">()</span> <span class="o">!=</span> <span class="n">QNetworkReply</span><span class="o">::</span><span class="n">NoError</span><span class="p">)</span>
<span class="n">qCritical</span><span class="p">(</span><span class="s">"Google Analytics Tracking failed with error: %s"</span><span class="p">,</span> <span class="n">reply</span><span class="o">-></span><span class="n">errorString</span><span class="p">());</span>
<span class="p">}</span>
<span class="nl">private:</span>
<span class="n">QNetworkAccessManager</span><span class="o">*</span> <span class="n">m_nam</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>Well then, this looks a lot better already! Now we are at least getting a message on stderr when our request fails for network and HTTP protocol reasons. We have also managed to encapsulate <code class="language-plaintext highlighter-rouge">QNetworkAccessManager</code>’s access if required. Be aware, though, that we are not just missing the actual payload validation, we have also introduced a defect if we share <code class="language-plaintext highlighter-rouge">QNetworkAccessManager</code> instances. I leave the fix for it as an exercise to the reader (I myself have just fixed this in the git repository today), but I will hint at the fact that one signal can have multiple slots connected to it, which should make the error very obvious.</p>
<p>The next step would now be to remove error potential from the data string we are passing in. For this I opted to turn some of the required parameters into member variables on the tracker class, and keep track of the others with a <code class="language-plaintext highlighter-rouge">QList<QPair<QString, QString>></code> that I pass into a <code class="language-plaintext highlighter-rouge">QUrlQuery</code> object for correct URL-encoding.</p>
<p>However, I think that I have written enough for this installment, and instead will focus my remaining spare time tonight on actually working on the library. The next part of this series will deal with the specifics of the query data and how to validate it so that we can avoid query errors like the one mentioned above. Until then feel free to leave me questions, criticisms and thoughts in the comments.</p>So far in this series I have only talked about the rudimentary infrastructure I used for setting up a continuous integration loop and a very specific problem to resolve before I could start test driven development. In this part, however, I want to share some more insight about the actual programming process so far. Note that while I will provide code samples, then I am going to omit accompanying test cases, mostly because I am not going to use. When you look at the protocol’s documentation you’ll quickly realize that it is nothing more than a url-encoded key value list that is being submitted to a specific webserver. This means we could simply use QNetworkRequest instances everywhere we want to track something, for example like this: QNetworkAccessManager g_nam; void foo() { QNetworkRequest req; req.setUrl("http://www.google-analytics.com/collect"); QString trackerQuery("v=1&cid=555&tid=UA-12345-678&t=event&ec=test&ev=1&ea=event1"); QByteArray data = trackerQuery.toLatin1(); g_nam.post(req, data); } // ... void bar() { QNetworkRequest req; req.setUrl("http://www.google-analytics.com/collect"); QString trackerQuery("v=1&cid=555&tid=UA-12345-679&t=event&ec=test&ea=event2"); QByteArray data = trackerQuery.toLatin1(); g_nam.post(req, data); } Now, as one can see there is a lot of duplication in this. The first obvious step here would be to take the trackerQuery string out of it: QNetworkAccessManager g_nam; void track(const QString& query) { QNetworkRequest req; req.setUrl("http://www.google-analytics.com/collect"); QByteArray data = query.toLatin1(); g_nam.post(req, data); } void foo() { track(QString("v=1&cid=555&tid=UA-12345-678&t=event&ec=test&ev=1&ea=event1")); } void bar() { track(QString("v=1&cid=555&tid=UA-12345-679&t=event&ec=test&ea=event2")); } This already avoids a lot of the code duplication, but still leaves us with a lot to be desired. For example, the QNetworkAccessManager instance is currently global, which means there is some shared state between the track() method and the rest of your application. Additionally we don’t even know if the request was sent at all because we are not listening to any of QNetworkAccessManager’s signals that would provide us with that information. Thirdly, the string we are passing into track() contains a lot of redundant inormation and there is no validation of whether it actually is valid according to the API’s or our requirements - for instance it is difficult to spot the typo that I made in bar()’s tracker ID. This particular issue could turn into a very difficult to debug problem that you probably won’t notice until the person who asked for that statistic to be collected comes asking why it does not show up with anything. So, let’s tackle these problems one by one. Or in this case, let’s just fix the first two problems in one go, really. For that we wrap the track method in a QObject-derived class, which also allows us to connect to the QNetworkAccessManager’s signals. class Tracker : public QObject { Q_OBJECT public: explicit Tracker( QObject* parent = nullptr ) : QObject(parent), m_nam(new QNetworkAccessManager(this)) { connect(m_nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*))); } void track( const QString& data ) { if ( ! m_nam ) { qCritical("No network manager specified!"); } QNetworkRequest req; req.setUrl("http://www.google-analytics.com/collect"); QByteArray data = query.toLatin1(); m_nam->post(req, data); } void setNetworkAccessManager(QNetworkAccessManager* nam) { if (m_nam && m_nam->parent() == this) { delete m_nam; } m_nam = nam; } public slots: void onFinished(QNetworkReply* reply) { reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) qCritical("Google Analytics Tracking failed with error: %s", reply->errorString()); } private: QNetworkAccessManager* m_nam; }; Well then, this looks a lot better already! Now we are at least getting a message on stderr when our request fails for network and HTTP protocol reasons. We have also managed to encapsulate QNetworkAccessManager’s access if required. Be aware, though, that we are not just missing the actual payload validation, we have also introduced a defect if we share QNetworkAccessManager instances. I leave the fix for it as an exercise to the reader (I myself have just fixed this in the git repository today), but I will hint at the fact that one signal can have multiple slots connected to it, which should make the error very obvious. The next step would now be to remove error potential from the data string we are passing in. For this I opted to turn some of the required parameters into member variables on the tracker class, and keep track of the others with a QList<QPair<QString, QString>> that I pass into a QUrlQuery object for correct URL-encoding. However, I think that I have written enough for this installment, and instead will focus my remaining spare time tonight on actually working on the library. The next part of this series will deal with the specifics of the query data and how to validate it so that we can avoid query errors like the one mentioned above. Until then feel free to leave me questions, criticisms and thoughts in the comments.Using Google Analytics Measurement Protocol with C++/Qt - A Software Engineering Exercise - Part 22014-03-18T22:39:53+00:002014-03-18T22:39:53+00:00https://methedrine.org/software%20development/2014/03/18/using-google-analytics-measurement-protocol-with-cqt-a-software-engineering-exercise-part-2<p>As I wrote in the previous article of this series, one of my goals with the Qt Google Analytics library for C++/Qt is to practice test driven development. From my perspective this is going to be a very educating and thus entertaining part of this whole undertaking. After all, unit testing in C++ can be difficult since the language can be so low level that it gets difficult to find suitable mock objects or stubs. And as if that would not be a challenge enough by itself, then it is a library that is going to communicate with a server on the other side of the internet. A server that is completely black box to us.</p>
<p>Normally this would make it pretty hard to write an automated test for. Don’t get me wrong, I’ve written such tests at CCP where we’d fire up a local webserver instance, or monkeypatch the relevant Python function so that expected responses for a given test request were returned. But this is either relatively slow since it requires starting and waiting for an external process, or it is very difficult to achieve because C++ is not a dynamic language like Python.</p>
<p>However, we can exploit some Qt and Google API specifics here to make our life a lot easier. For a start, the Google API does not send any response codes and just ignore malformed requests, which means that our library does not have to handle error responses - simply because it cannot - and thus sending expected responses is impossible. Additionally, it is considered good practice in Qt to only have a single <code class="language-plaintext highlighter-rouge">QNetworkAccessManager</code> instance in your application, and since that class is the base for HTTP based network communication in Qt then our library needs to be able to use an external instance of it. Hooray, there is our requirement to provide the library with an API that might as well take a mock object derived <code class="language-plaintext highlighter-rouge">QNetworkAccessManager</code>! Conveniently that Qt class also contains a protected virtual function that processes a <code class="language-plaintext highlighter-rouge">QNetworkRequest</code>, the method via which it was sent and its associated payload to constructs a corresponding <code class="language-plaintext highlighter-rouge">QNetworkReply</code> object. That makes it a gold mine right there: All we need to provision is <code class="language-plaintext highlighter-rouge">QNetworkAccessManager</code> subclass where we override the <code class="language-plaintext highlighter-rouge">createRequest()</code> method and check our expectations from within there. Look how simple this gets to validate a request our library is going to send:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">QNetworkReply</span><span class="o">*</span> <span class="n">TestNetworkAccessManager</span><span class="o">::</span><span class="n">createRequest</span><span class="p">(</span> <span class="n">QNetworkAccessManager</span><span class="o">::</span><span class="n">Operation</span> <span class="n">op</span><span class="p">,</span> <span class="k">const</span> <span class="n">QNetworkRequest</span><span class="o">&</span> <span class="n">request</span><span class="p">,</span> <span class="n">QIODevice</span> <span class="o">*</span><span class="n">outgoingData</span> <span class="p">)</span>
<span class="p">{</span>
<span class="c1">// Url, headers or metadata do not match</span>
<span class="n">m_failed</span> <span class="o">=</span> <span class="p">(</span> <span class="n">request</span> <span class="o">!=</span> <span class="o">*</span><span class="n">m_expectedRequest</span> <span class="p">);</span>
<span class="c1">// payload mismatch</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">outgoingData</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">QByteArray</span> <span class="n">outgoing</span> <span class="o">=</span> <span class="n">outgoingData</span><span class="o">-></span><span class="n">readAll</span><span class="p">();</span>
<span class="n">m_failed</span> <span class="o">|=</span> <span class="p">(</span> <span class="n">outgoing</span> <span class="o">!=</span> <span class="n">m_expectedData</span> <span class="p">);</span>
<span class="p">}</span>
<span class="c1">// operation mismatch</span>
<span class="n">m_failed</span> <span class="o">|=</span> <span class="p">(</span> <span class="n">op</span> <span class="o">!=</span> <span class="n">m_expectedOp</span> <span class="p">);</span>
<span class="k">return</span> <span class="n">QNetworkAccessManager</span><span class="o">::</span><span class="n">createRequest</span><span class="p">(</span> <span class="n">op</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">outgoingData</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Our test can then simply check the failed flag to indicate success or failure. There is some obvious room for improvement - for example this method could write out a helpful message indicating where exactly the test failed. But overall this appears to me as a very elegant, to the point solution to the problem of verifying that the library is sending requests in a format that the Google Analytics backend expects.</p>
<p>Next up I should probably talk about some of the design choices I took or am taking for the library, and lay out my reasoning behind them, so that will be the content for part 3.</p>As I wrote in the previous article of this series, one of my goals with the Qt Google Analytics library for C++/Qt is to practice test driven development. From my perspective this is going to be a very educating and thus entertaining part of this whole undertaking. After all, unit testing in C++ can be difficult since the language can be so low level that it gets difficult to find suitable mock objects or stubs. And as if that would not be a challenge enough by itself, then it is a library that is going to communicate with a server on the other side of the internet. A server that is completely black box to us.Using Google Analytics Measurement Protocol with C++/Qt - A Software Engineering Exercise - Part 12014-03-18T00:56:05+00:002014-03-18T00:56:05+00:00https://methedrine.org/software%20development/2014/03/18/using-google-analytics-measurement-protocol-with-cqt-a-software-engineering-exercise-part-1<p>As a programmer working on video games it is difficult to practice “proper” software engineering methods most of the time. From my experience, the reasons for this are numerous. For a start, not a lot of programmers I worked with have the knowledge or interest in working that way. Maybe there were bad experiences with needlessly over-engineered code. Production does not care too much about it either as long as features get completed and defects resolved, even if the resulting code is bad. Burn-down charts and hitting dead-lines is what counts instead. Last but not least management is not interested in these numbers because for them, as long as the product “looks good, it is good”. And it is not like anybody could easily pin a financial benefit of following practices - rather on the contrary, all of a sudden there is need to purchase some extra software licenses. In short, following software engineering practices can be a very hard sell in this environment. Truth be told, I’ve rarely been willed to push for the application of software engineering principles myself, simply because I could not be bothered with the political game involved.</p>
<p>But anyway. With having recently started a new job and having been professionally developing software for almost 10 years now, I felt the need to challenge myself again a little bit and actually exercise good engineering principles when programming. And to add some extra pressure onto myself to stick to this principle, I’ve decided to document the processes I’ll use, so that it will be useful even to the novice software developer.</p>
<p>As a first such exercise I decided to write a C++ library to track usage patterns of a Qt desktop application using the <a href="https://developers.google.com/analytics/devguides/collection/protocol/v1/" title="Google Analytics Measurement Protocol Overview">Google Analytics Measurement Protocol</a>. To do this, I began by setting up <a href="https://github.com/daehlith/QtGoogleAnalytics" title="The GitHub repository for QtGoogleAnalytics - a C++ library for integrating Google Analytics in a Qt application.">a GitHub repository</a>, because having a version control system should be considered as the bare minimum requirement along side a good IDE when writing software . The second principle I set to myself was to use test-driven development. I’ve more and more become a fan of this development model since it helps you gain a lot of confidence in what you are building. However, it does add some overhead as that you are no longer just compiling, but also running a small test application continuously. This overhead can easily be addressed in your favourite IDE by specifying adequate post-build steps, which I will leave as an exercise to the reader, but ideally you’d want to have these tests also run when you commit your changes to your code repository. After all, nothing is more annoying than breaking a build because you forgot to submit a file and not discovering it until you (or someone else) pulls the latest changes on a different machine. Since I already know that I will also want to run a few more things in an automated fashion (static code analysis, doxygen), I’ve opted for setting up what is called a “continuous integration loop” using the <a href="https://travis-ci.org/daehlith/QtGoogleAnalytics" title="Continuous integration with Travis CI">Travis CI</a> platform which is freely available for open source projects. Since Travis does C++ compilation on Ubuntu using gcc and/or clang then there is even the added bonus of getting some cross-platform exposure for my code since I personally develop on a Windows system at the moment. In other words: Even more win for your code!</p>
<p>Setting up Travis CI for a GitHub project is dead simple and straight forward. Once you’ve logged into Travis using your GitHub account (hooray for OAuth!), you can select which of your projects should be observed by Travis. After selecting your project, all you need to do is provision a file with the name .travis.yml which contains the build instructions for your project. One thing that you’ll want to pay attention to is that Travis always spins up a clean virtual machine for your build, so you will need to include commands for installing required Ubuntu packages. Feel free to take a look at the configuration I provisioned for my little project.</p>
<p>Now, I’m all set up for developing my project and it’s time to write some code to see how this plays together. But wait, this is going to be a library communicating via HTTP, how do I write tests for this? Well, it turns out this is rather easy in the end, but it took me a moment to figure out a good solution. And I’ll tell you more about that in the installment of this series.</p>As a programmer working on video games it is difficult to practice “proper” software engineering methods most of the time. From my experience, the reasons for this are numerous. For a start, not a lot of programmers I worked with have the knowledge or interest in working that way. Maybe there were bad experiences with needlessly over-engineered code. Production does not care too much about it either as long as features get completed and defects resolved, even if the resulting code is bad. Burn-down charts and hitting dead-lines is what counts instead. Last but not least management is not interested in these numbers because for them, as long as the product “looks good, it is good”. And it is not like anybody could easily pin a financial benefit of following practices - rather on the contrary, all of a sudden there is need to purchase some extra software licenses. In short, following software engineering practices can be a very hard sell in this environment. Truth be told, I’ve rarely been willed to push for the application of software engineering principles myself, simply because I could not be bothered with the political game involved.Programming a STC 1000 temperature controller2013-11-02T23:39:47+00:002013-11-02T23:39:47+00:00https://methedrine.org/home%20brewing/2013/11/02/programming-a-stc-1000-temperature-controller<p>If you are like me then you are probably rarely changing the temperature on it, so it’s easy to forget how the controller actually has to be used. In that case, <a href="https://www.homebrewtalk.com/f39/help-stc-1000-programming-406818/#post-5136136" title="Programming the STC 1000">this post on homebrewtalk.com</a> is your friend. While it does not explain all the functionality, then it does actually summarize how you program the controller in a better way than the actual manual.</p>If you are like me then you are probably rarely changing the temperature on it, so it’s easy to forget how the controller actually has to be used. In that case, this post on homebrewtalk.com is your friend. While it does not explain all the functionality, then it does actually summarize how you program the controller in a better way than the actual manual.