Stichword-Archiv: software development

Do Not Use Boolean Parameters

Mai 31, 2017 7:16 pm Veröffentlicht von

I’m absolutely serious, don’t do it. “But hey”, one may ask, “what’s wrong with boolean parameters?” That’s what I got asked recently when I did a code review. And to all of you who have the same question in mind, here is my answer.
There is only one reason to use a boolean parameter in a function interface, and that’s when it’s the only parameter – in a setter function (and even there one could argue to not use it).

Unreadable Function Calls

Let’s take a look at a first example:

 1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include "concat.h"
#include <string>
#include <vector>

int main ()
{
Concat concat;
std::vector<const std::string> values {"Line One", "Line Two", "Line Three"};
std::cout << concat.concatVector(values, false, true);
return 0;
}

In line 10 you can see a call to the function concatVector which seems to be used to concat all the values of a vector to a single string. But what are those 2 boolean parameters passed? To find out what they are used for you have to take a look at the declaration of that function. (Or, depending on the IDE you are using, at least hover over the function call to see the names of that parameters, while hoping the names are speaking) Here is the corresponding header file:

1
2
3
4
5
6
7
#include <vector>
class Concat {
public:
const std::string concatVector(const std::vector<const std::string> strings, const bool unixStyle, const bool endWithNewline);
private:
const std::string newLine(const bool unixStyle);
};

Okay. Now we know the first one is used to tell the function if it should use unix-style new lines to separate the strings or DOS-style new lines. The second one tells if the string should end with a newline. Took already some time, and it will take that time again when you want to understand it a week later, because you won’t remember at least the order of those parameters.
One first step towards better readability of that code would be to store the parameters first to local variables, so the reader of the code knows what they are used for.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include "concat.h"
#include <string>
#include <vector>

int main ()
{
Concat concat;
std::vector<const std::string> values {"Line One", "Line Two", "Line Three"};
bool useUnixStyle = false;
bool endWithNewLine = true;
std::cout << concat.concatVector(values, useUnixStyle, endWithNewLine);
return 0;
}

Okay, now we can at least understand what the code does, without visiting the header or implementation of that function. Great.

Even better would be to make them member of the Concat class, so the user of the class is forced to explicitly spell the usage in the client code:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include "concat.h"
#include <string>
#include <vector>

int main ()
{
Concat concat;
std::vector<const std::string> values {"Line One", "Line Two", "Line Three"};
concat.setUseUnixStyle(false);
concat.setEndWithNewline(true);
std::cout << concat.concatVector(values);
return 0;
}

That way, you even can specify suitable defaults for that boolean parameters without setting them in the function definition (which you also shouldn’t do, but that’s a separate topic).

 1
2
3
4
5
6
7
8
9
10
11
#include <vector>
class Concat {
public:
const std::string concatVector(const std::vector<const std::string>& strings);
void setUseUnixStyle(bool _useUnixStyle){useUnixStyle = _useUnixStyle;};
void setEndWithNewline(bool _endWithNewline){endWithNewline = _endWithNewline;};
private:
const std::string newLine();
bool useUnixStyle {false};
bool endWithNewline {false};
};

Multi-purpose functions

Often, boolean parameters are used to specify the behaviour of a function. This indicates that the function in question may serve multiple purposes, which functions should never do. Hence, often it is useful to split those functions up in separate ones, each fulfilling one purpose. This results in a cleaner interface of the functions as well as easier to understand function implementations.
Here is the current implementation of the Concat class.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <string>
#include <sstream>
#include "concat.h"

const std::string Concat::concatVector(const std::vector<const std::string>& strings)
{
std::stringstream str;
for (std::vector<const std::string>::const_iterator it = strings.begin(); it != strings.end(); ++it) {
if (it != strings.begin())
str << newLine();
str << *it;
}
if (endWithNewline)
str << newLine();
return str.str();
}

const std::string Concat::newLine() {
if (useUnixStyle)
return "n";
else
return "rn";
}

We now split it up into two separate functions concatVectorWithNewlineEnding and concatVectorWithoutNewlineEnding (one might come up with better function names).

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <string>
#include <sstream>
#include "concat.h"

const std::string Concat::concatVector(const std::vector<const std::string>& strings)
{
if (endWithNewline)
return concatVectorWithNewlineEnding(strings);
return concatVectorWithoutNewlineEnding(strings);
}

const std::string Concat::concatVectorWithNewlineEnding(const std::vector<const std::string>& strings)
{
const auto& concatWithoutNewline = concatVectorWithoutNewlineEnding(strings);
return appendNewline(concatWithoutNewline);
}

const std::string Concat::concatVectorWithoutNewlineEnding(const std::vector<const std::string>& strings)
{
std::stringstream str;
for (std::vector<const std::string>::const_iterator it = strings.begin(); it != strings.end(); ++it) {
if (it != strings.begin())
str << newLine();
str << *it;
}
return str.str();
}

const std::string Concat::appendNewline(const std::string& string)
{
std::string str(string);
return str.append(newLine());
}

const std::string Concat::newLine() {
if (useUnixStyle)
return "n";
else
return "rn";
}

Now we have separate functions, each serving one purpose only, which play well together. Even though we generated more code now than in the beginning, it is much more readable and every function does exactly what its name promises.

Summary

Here are the main reasons not to use boolean parameters in your functions:
  1. Code which uses your function is easier to read if you find a different way than boolean parameters
  2. Boolean parameters indicate functions with more than one purpose, which you should split up whenever possible
I used a simple example to show how you can improve such code. One can imagine, the effect gets even bigger in a huge code base with long and unreadable functions (and a high amount of function parameters).

Putting tomcat into a container

Februar 9, 2017 10:37 pm Veröffentlicht von

As soon as you get in touch with software there is no chance to get around one topic these days: Microservices. And wherever you hear this term there is always a second one around the corner: Docker. I myself have only little experience in productively using docker, and this is mostly restricted to the convenience of packaging stuff and installing it somewhere else via a central repository. But already this is quite cool, so why not try to go further and create a small Java service in a docker container if setting up things is so easy with docker. The goal of this post is to document how I achieved a first response of the service – setting up a Java microservice with docker should be almost no work, right? As I haven’t had much contact with tomcat and maven, I learned all this (not much in the end but it took some time) the hard way – fighting all the way through old forum posts, maven repositories, eclipse UIs, tomcat books and documentations. Please let me know if you see any potential improvements.

Setting things up

Here we go. First of all I created a docker-compose file that shows how my microservice-powered project will look like:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
proxy:
image: nginx
links:
- frontend
- backend
volumes:
- ./proxy/nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- "80:80"
frontend:
image: nginx
volumes:
- ./frontend:/usr/share/nginx/html
backend:
image: tomcat
links:
- database
volumes:
- ./backend/tomcat-users.xml:/usr/local/tomcat/conf/tomcat-users.xml
database:
image: mysql
environment:
- MYSQL_ROOT_PASSWORD=topsecret
- MYSQL_DATABASE=mydb
- MYSQL_USER=mydbuser
- MYSQL_PASSWORD=

I know container-links are not the most modern way to go in the docker universe, but for the ease of use and to show how things are tangled together, I still use them for this small project. As you can see, we have four different containers. One for the frontend, containing the great UI of our application. A second one containing the database with all the data the application will need to run. Don’t worry, I won’t bother you with boring database setup in this blog post. It’s only about the third thing: the tomcat container. For this container we add a link to the database, of course, and we mount a local file to override the user configuration of the tomcat.
This is needed to be able to show the manager application and to deploy remotely to the tomcat server. Here is the config file as I created it. (I copied it out of the container and adjusted it, maybe you should do the same to make sure it’s compatible with your version of tomcat. And, for productive usage, of course you should use a password for the admin user. But configuring maven is more complicated then, so for now it’s ok without for me.)

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version='1.0' encoding='utf-8'?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<!--
NOTE: By default, no user is included in the "manager-gui" role required
to operate the "/manager/html" web application. If you wish to use this app,
you must define such a user - the username and password are arbitrary. It is
strongly recommended that you do NOT use one of the users in the commented out
section below since they are intended for use with the examples web
application.
-->
<!--
NOTE: The sample user and role entries below are intended for use with the
examples web application. They are wrapped in a comment and thus are ignored
when reading this file. If you wish to configure these users for use with the
examples web application, do not forget to remove the <!.. ..> that surrounds
them. You will also need to set the passwords to something appropriate.
-->
<!--
<role rolename="tomcat"/>
<role rolename="role1"/>
<user username="tomcat" password="<must-be-changed>" roles="tomcat"/>
<user username="both" password="<must-be-changed>" roles="tomcat,role1"/>
<user username="role1" password="<must-be-changed>" roles="role1"/>
-->
<role rolename="manager-script"/>
<user username="admin" password="" roles="manager-script"/>
<role rolename="manager-gui"/>
<user username="tomcat" password="topsecret" roles="manager-gui"/>
</tomcat-users>

The last container is used to reverse-proxy the requests of the application either to the frontend or to the backend, depending on the URL. In this example, I configured nginx to lead all the requests starting with /backend (http://localhost/backend) or /manager to the backend container (make sure to set it to the correct port of tomcat, 8080) and all others to the frontend container. So we need to make sure the servlet we are going to create is available under /backend, so they can be routed through the reverse proxy correctly.
We run docker-compose up -d as usual and some seconds later the containers are up and running. If you now go to http://localhost/backend you should already see tomcat greeting you with a 404 error page. Installing tomcat has never been so easy.

Deploying to tomcat

Next, we want to be able to deploy a servlet to the tomcat running in the docker container. To do this, I decided to use maven to build and deploy the project. To get the initial setup, I ran maven with the following command line:
mvn archetype:generate -DgroupId=com.ntlx -DartifactId=backend -DarchetypeArtifactId=maven-archetype-webapp
After the execution of this command you will find your self in a folder structure, containing all the files and folders you always wondered where they come from, and who knows where to put them:

./pom.xml
./src
./src/main
./src/main/resources
./src/main/webapp
./src/main/webapp/index.jsp
./src/main/webapp/WEB-INF
./src/main/webapp/WEB-INF/web.xml

The pom.xml now needs some further adjustments because we want to deploy to a remote tomcat server. Here is the complete file, as it looks for me:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ntlx</groupId>
<artifactId>backend</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>backend Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>


</dependencies>
<build>
<finalName>backend</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>true</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<url>http://localhost/manager/text</url>
<server>TomcatServer</server>
<path>/backend</path>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<silent>true</silent>
<artifactItems>
<artifactItem>
<groupId>javax</groupId>
<artifactId>javaee-endorsed-api</artifactId>
<version>6.0</version>
<type>jar</type>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

It already contains the plugins needed to deploy a servlet and it contains the information to deploy onto a remote tomcat. In our case the remote tomcat is localhost, port 80 (remember: reverse-proxied to port 8080 in the tomcat container). /manager/text is needed to deploy our service. That’s why we also created a reverse-proxy for this URL.

That’s nearly it. If we now run
mvn tomcat7:deploy
it should be able to deploy our web application to the tomcat server. Go to http://localhost/backend again to see the hello world page, maven created earlier.

Adding a servlet

To provide our RESTful service in the end, we want to use a servlet. Therefore, we need to create a .java file for our servlet class at the right place: src/main/java. Create this folder, add some subfolders for your package like com/ntlx/ and add a Java Servlet file there. I myself copied the example from the eclipse wizard for servlet creation:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
package com.ntlx;

/**
* Servlet implementation class FirstTest
*/
@WebServlet("/FirstTest")
public class FirstTest extends HttpServlet {
private static final long serialVersionUID = 1L;

/**
* Default constructor.
*/
public FirstTest() {
// TODO Auto-generated constructor stub
}

/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.getWriter().append("Served at: ").append(request.getContextPath());
}

/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}

}

As you can see, we set the URL to /FirstTest. This is the relative address where the servlet will be found. So, after a run of 
mvn tomcat7:redeploy

you should see the response of the servlet at http://localhost/backend/FirstTest

That’s it, now we can start developing the service! I think this is quite handy as you can deploy the same tomcat on a different machine, like the productive one instead of your developer notebook, and it will work as before. Of course we have to think of a process to package the servlet with the docker container, but this shouldn’t be too much effort.