Growing Object-Oriented Software, Guided by Tests (Freeman/Pryce)

Nachdem “Growing Object-Oriented Software, Guided by Tests” von Steve Freeman und Nat Pryce bei den Ruby Rogues besprochen wurde (siehe 068 RR Book Club: Growing Object Oriented Software Guided by Tests), war mir klar, dass ich es auch lesen musste. Und ich muss sagen: es hat sich definitiv gelohnt. Es ist das beste Buch zum Thema Test Driven Development, das ich bislang gelesen habe.

Das Buch beginnt mit einer ca. 70-seitigen allgemeinen Einführung in das (testgetriebene) Vorgehen der Autoren bei der Softwareentwicklung. Dabei wird z.B. TDD erläutert und die eingesetzten Werkzeuge werden vorgestellt. Der Fokus liegt hierbei auf JUnit und jMock.

Danach folgt ein riesiges Praxisbeispiel: ein “Auktionssniper”, dessen Entwicklung als Java-Swing-Anwendung auf gut 150 Seiten beschrieben wird. Was mir dabei die Augen geöffnet hat, war das Starten mit einem fehlschlagenden Akzeptanztest. Bevor die erste Zeile Produktiv- bzw. Unit-Test-Code geschrieben wurde, haben die Autoren viel Zeit darauf verwendet, einen “End-To-End-Test” zu erstellen, der die erste Funktionalität der Applikation wie ein echter Benutzer testet. Dazu ist einiges an Aufwand nötig (Threading, GUI-Test-Framework etc.), aber das lohnt sich im Nachhinein, weil die Tests mit echten Benutzern entfallen können.

Ich muss zugeben, dass ich das Praxisbeispiel teilweise nur recht schwer nachvollziehen konnte, weil ich beim Lesen nicht den ganzen Code im Kopf hatte und immer nur die interessanten Teile gezeigt wurden. Allerdings konnte ich sehr viele Anregungen für meine eigenen Anwendungen mitnehmen, z.B. dass man generische Klassen nicht als Methodenparameter nutzen sollte (sondern besser eine eigene Containerklasse mit einem sinnvollen Namen erstellen sollte), dass einem die “import”s einer Klasse ggfs. verraten, dass diese zu viele Aufgaben hat, oder dass man Factorys besser im Kontext der Domäne benennen sollte (z.B. AuctionHouse anstatt AuctionFactory).

Abgerundet wird das Buch durch die letzten ca. 100 Seiten mit wieder eher allgemeinen Tipps zum testgetriebenen Entwickeln. Dabei gehen die Autoren noch einmal in die Vollen und behandeln z.B. das Testen von Multithreading-Applikationen oder den sinvollen Umgang mit Logging-Mechanismen.

Alles in allem kann ich dieses Buch jedem Softwareentwickler empfehlen. Obwohl die Beispiele in Java geschrieben sind, lassen sie sich leicht auf andere Sprachen übertragen und jeder Entwickler wird hier eine Vielzahl an Anregungen für die Programmierung mitnehmen können.

Enable daily Java Updates in Windows 7 (64bit)

Java has had quite a few security issues recently. Therefore, it definitely makes sense to enable the Java Updater and to install every Java update right after Oracle releases it. However, on my different Windows (64bit) boxes I could not configure the Java Updater. There was simply no “Update” tab in the Java control panel.

I found the solution here: Missing Java update tab. Possibly due to permission problems, Java did not add the following key to the registry: HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Update\Policy\EnableJavaUpdate. After I added it (DWORD, value 1), the “Update” tab was shown, but I was not able to save the changes I made to the update interval. The solution to this problem can be found here: Why are the Java update settings not saved in the Java control panel?. You have to start the Java control panel (javacpl.exe) as an Administrator.

To save me the pain of repeating the steps above on all my Windows machines, here is a file that can be imported directly into the Windows registry and sets the update interval to daily (9:00 AM):

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Update\Policy]
"EnableJavaUpdate"=dword:00000001
"NotifyDownload"=dword:00000000
"NotifyInstall"=dword:00000001
"Frequency"=dword:0000007f
"UpdateSchedule"=dword:00000009

Download the file: Enable daily Java updates in Windows registry.

Database is locked when committing to a Subversion repository

I added a new Subversion repository on our Debian webserver today and got the following error messages when committing to it:

[Wed Oct 10 ...] [error] [client ...] mod_dav_svn close_stream: error closing write stream  [500, #200029]
[Wed Oct 10 ...] [error] [client ...] Couldn't perform atomic initialization  [500, #200029]
[Wed Oct 10 ...] [error] [client ...] database is locked  [500, #200030]

Our repositories are hosted on a Windows fileserver and are mounted to the local Linux filesystem. In my case, the solution to the problem was to mount the Samba share using the parameters nolock,rw in /etc/fstab like this:

//fileserver/SVNRepository    /home/svn    smbfs    user=username,pass=secret,dom=MYDOM,uid=33,gid=33,nolock,rw    0    0

System error 5 when accessing a network share on Windows 2008 using a DNS alias name

When I tried to access a network share on a Windows 2008 server locally (!) today using a DNS alias I configured, I got System error 5 has occurred. Access is denied (Systemfehler 5 aufgetreten. Zugriff verweigert in German):

Accessing a network share locally on a Server 2008 fails

As you see in the screenshot, the shares are enumerated correctly when using the server’s computer name (server), but access is denied when using the alias name (fileserver). However, from a client machine running Windows 7, I was able to enumerate the shares both using server and fileserver.

I found the solution to this problem in Microsoft’s Knowledge Base: Error message when you try to access a server locally by using its FQDN or its CNAME alias after you install Windows Server 2003 Service Pack 1: “Access denied” or “No network provider accepted the given network path”:

Add the FQDN (!) of the server’s alias (e.g. fileserver.domain.local) to the value of the registry key BackConnectionHostNames under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0.

In my case, the share was accessible instantly (without restarting the server).

Migrating an existing Scuttle database to SemanticScuttle

I’ve migrated my existing bookmark collection from Scuttle to SemanticScuttle. All it took was these three lines of SQL:

insert into semanticscuttle.sc_users (uId,username,password,uDatetime,uModified,name,email,homepage,uContent) (select * from scuttle.sc_users); insert into semanticscuttle.sc_bookmarks (bId,uId,bIp,bStatus,bDatetime,bModified,bTitle,bAddress,bDescription,bHash) (select * from scuttle.sc_bookmarks); insert into semanticscuttle.sc_bookmarks2tags (select * from scuttle.sc_tags);

PowerShell: Configure some basic Windows settings after a fresh installation

After installing Windows I always started configuring the same old basic settings:

  • move Desktop, Documents, Music, Videos, Pictures folders to D:\
  • configure some basic settings in Windows Explorer folder options (e.g. show hidden files, show file extensions)
  • remove Internet Explorer from taskbar and add PowerShell to it

Today I set up a few virtual machines and decided to finally script the above steps with PowerShell to save some time. Here’s the final script:
function changeUserFolders() { $profileRoot = "D:\Profil\Stefan\"; $folderSettings = @{ "Desktop" = "Desktop"; "Personal" = "Dokumente"; "My Music" = "Musik"; "My Pictures" = "Bilder"; "My Video" = "Videos"; "{374DE290-123F-4565-9164-39C4925E467B}" = "D:\Download"; }; $regkeys = @("HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders", "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"); foreach ($key in $folderSettings.keys) { $path = $folderSettings[$key]; if ($path -notmatch ":") { $path = [System.IO.Path]::Combine($profileRoot, $path); } if (!(test-path $path)) { mkdir $path; } foreach ($regkey in $regkeys) { set-ItemProperty -path $regkey -name $key $path; } } cd (gc env:\userprofile); foreach ($dir in @("Desktop", "Documents", "Downloads", "Music", "Pictures", "Videos")) { del $dir -force -recurse; } } function setExplorerSettings() { $settings = @{ "Hidden" = 1; "SuperHidden" = 1; "HideFileExt" = 0; "WebView" = 0; "SeparateProcess" = 1; "DontPrettyPath" = 1; "SharingWizardOn" = 0; "AlwaysShowMenus" = 1; "HideDrivesWithNoMedia" = 0; "NavPaneShowAllFolders" = 1; "NavPaneExpandToCurrentFolder" = 1; }; foreach ($key in $settings.keys) { Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -name $key -value $settings[$key]; } stop-process -processname explorer } function getVerbs($dir, $file) { $shell = new-object -com "Shell.Application"; $folder = $shell.Namespace($dir); $item = $folder.Parsename($file); return $item.Verbs(); } function executeVerb($dir, $file, $verb) { $verb = (getVerbs $dir $file) | ? {$_.Name -eq $verb}; if ($verb) { $verb.DoIt(); } } function pinToTaskbar($dir, $file) { executeVerb $dir $file "An Tas&kleiste anheften"; } function unpinToTaskbar($dir, $file) { executeVerb $dir $file "Von Tas&kleiste lösen"; } function createLinks() { $winDir = gc env:\SystemRoot; $progDir = gc "env:\ProgramFiles"; $prog86Dir = gc "env:\ProgramFiles(x86)"; $psDir = [System.IO.Path]::Combine($winDir, "system32\WindowsPowerShell\v1.0"); $ieDir = [System.IO.Path]::Combine($progDir, "Internet Explorer"); $ie86Dir = [System.IO.Path]::Combine($progDir, "Internet Explorer"); pinToTaskbar $psDir "powershell.exe"; unpinToTaskbar $ieDir "iexplore.exe"; unpinToTaskbar $ie86Dir "iexplore.exe"; } changeUserFolders; setExplorerSettings; createLinks;

Oracle: SELECT top N rows skipping the first X rows

Apparently, it is not possible to use Oracle’s pseudo column rownum to select rows after a given number of rows, e.g. like
SELECT * FROM tab WHERE rownum > 100;
or in MySQL
SELECT * FROM tab LIMIT 100,50;
Oracle’s explanation for this (from the DB reference: ROWNUM):

The first row fetched is assigned a ROWNUM of 1 and makes the condition false. The second row to be fetched is now the first row and is also assigned a ROWNUM of 1 and makes the condition false. All rows subsequently fail to satisfy the condition, so no rows are returned.

However, to skip the first X rows in your query, you can make it a subquery like this (be sure to alias rownum!):
SELECT * FROM (SELECT field1, field2, ROWNUM rn FROM tab) WHERE rn > 100;

Git ignores .gitignore when working with PowerShell

Today, a Git problem drove me nuts: I added files to the .gitignore file to make sure Git does not track and commit them to the repository. However, Git stubbornly ignored the file and kept adding all the files in my directory to the index.

As it turned out, PowerShell was the problem. I added the files in .gitignore with a simple echo test.txt > .gitignore. I got suspicious when I saw the file’s size and remembered that PowerShell uses UTF-16 to handle Strings. When editing the file in an external editor, Git recognized the content and ignored the files.

So, the solution for me was to use an external text editor for .gitignore or explicitly change the encoding when adding an entry via PowerShell using ac -path .gitignore -Value "test.txt" -Encoding ascii

Git ignores .gitignore in PowerShell due to UTF-16 encoding

PowerShell: Check whether a Windows command or executable is available

In one of my recent PowerShell scripts I needed to find out if certain commands (in my case “svn”, “git”, “ant” and “mvn”) were available or not. I wrote this small function that tries to call the given command and returns whether it is callable.

function commandAvailable($cmd, $options) { $error.clear(); $ErrorActionPreference = "silentlycontinue"; & $cmd $options | out-null; $ErrorActionPreference = "stop"; if ($error[0]) { write-host ("Command " + $cmd + " " + $options + " is not available") -foregroundcolor "red"; return $false; } return $true; } write-host (commandAvailable "svn" "--version");