Let's Mess Up Spaces (Java Space Program). Yes, let's!

 

Let's Mess Up Spaces (Java Space Program). Yes, let's!

Premise

Okay, so I wanted to try something. This week I gave myself five hours to create a program from start to finish. I’m going to walk you through my process and here’s a link to the relevant github repository.

Have you ever been in an argument about which programming language is better? And you occasionally get that dude who thinks that scripting languages are the best and just can’t shut up about how amazing Python is? No? Well, neither have I, but I’m assuming these things happen. And because Python is fussy about whitespace let’s be a little bit evil and make a program that would go through a given file and replace all normal whitespace characters with fancy whitespaces, of which there are sixteen. Yeah, I think that’s how you become a pretty mediocre villain.

Because of the five hour time limit it’s not necessarily in a state that I would have liked to leave it in all things being equal, but it does work, it has tests, and it’s available for everyone to play around with and improve. It that’s your cup of tea that is.

It will probably quickly become overwhelming if I go through every single line of code I wrote, so I’m just going to rush through it. If you missed the first link - you can take a look at the code here.

Preparations/Structure

Let’s imagine how the program will work first. The user gives it a file, it reads the file’s contents and replaces normal spaces with all the atypical spaces, it saves the file.

To do that we need:

  1. A list of all the whitespace characters we would like to use. Because I don’t get to use enums often enough in real life - let’s use those.
  2. A simple way to get a random fancy whitespace character. Let’s have a class whose sole responsibility will be to return that random fancy whitespace character or, what the hell, a default space.
  3. A way to go through text/code and find spaces. Let’s immediately make two assumptions: first - this class wouldn’t care about files, it will be getting text in a form of string; second - when looking at characters one-by-one if a whitespace character is found - go to the implementation of 2. and replace the found space with a random one. Because I’m not really a bad person - let’s also do the reverse - if we come across a non-default whitespace character - let’s be able to replace it with the default one.
  4. Logically following from 3. we need a utility to load the contents of a file into a string, and, while we’re at it - save a string into a squeaky clean new file.
  5. We need some sort of a way for the user to interact with the program. Since I don’t want to spend too much time on this - command line should do.
  6. Finally, come up with a moderately punny name. (‘Space Program’ - boom, done!)

Specifics

  1. Because the post has ‘Java’ in the title - ‘Space Program’ will be written in Java.
  2. Testing is good. Test Driven Development is even better, it allows us to have our code shaped by our assumptions and we immediately know if it’s working as we write it. So we won’t end up writing thousands of lines of code only to find out that when we’re finally able to run the code - it doesn’t work the way we expect it to. So yeah, let’s do that.
  3. Clean architecture is also neat. I’ll try and create as much separation between all the different aspects of functionality as I can think of.
  4. I really don’t want to reinvent the wheel, so I’ll focus on writing functionality for the thing I want the program to do (replace default whitespace with non-default whitespace and back) and use existing libraries for the rest (reading files, parsing command line arguments, etc). Apache’s FileUtils and CLI will fit in nicely.

Let’s go

So the first thing I did was create a Whitespace enum. It’s extremely simple and doesn’t really require a test or a lot of explanation. Mainly because it doesn’t do much. It contains all the whitespace characters I wanted to use.

Then I wrote my first test:

	@Test
	public void getRandomWhitespaceTest() {
		SpacesStore store = new SpacesStore();
		assertTrue(store.getRandomWhitespace() instanceof Whitespace);
	}

This was before I created a SpacesStore class. So understandably not only the test didn’t pass, it didn’t even compile. I find there is something extremely satisfying about having a bunch of errors in a test go away as you’re creating the functionality it’s supposed to be testing. So, obviously, my next step was to write this:

	public class SpacesStore {
		private Random rand = new Random();
		private Whitespace whitespace;
		private final List<Whitespace> values = 
				Collections.unmodifiableList(Arrays.asList(Whitespace.values()));

		public Whitespace getRandomWhitespace() {
			return values.get(rand.nextInt(values.size()));
		}
	}

Then I thought that there is probably little need to actually return a Whitespace, why not have a method that will return a character?

	@Test
	public void getRandomSpaceTest() {
		Character spaceValue;
		SpacesStore store = new SpacesStore();
		spaceValue = store.getRandomSpace();
		assertTrue(spaceValue != null);
		assertTrue(!spaceValue.equals(""));
	}

Now, admittedly, this test can be better. It’s just making sure that we’re getting a non-null character, it doesn’t check that the character is one of the defined Whitespaces. Then getRandomSpace was created. After that, the next test was to check that the method for returning the default whitespace character was doing that. So I wrote the test and then the corresponding functionality.

After that I felt that this is pretty much all the functionality I want the SpaceStore class to have. The next step would be to have a TextManipulator class that will do just what it says on the tin. This is the bit of the program that covers the main concept. You give this class some text and it finds spaces in it and replaces them with random spaces helpfully provided by the SpacesStore class. It essentially had to have two methods - one for replacing all normal spaces with the weird ones and another for doing exactly the opposite (because I’m a sucky villain).

So the next test was:

	@Test
	public void replaceDefaultSpacesTestTextWithSpaces() {
		String result = "";

		TextManipulator manipulator = new TextManipulator();
		result = manipulator.replaceDefaultSpaces(textWithDefaultSpaces);

		assertTrue(!result.equals(textWithDefaultSpaces));
	}

Then followed the actual method, the errors went away and the test passed. You could ask why didn’t I just check whether result contained any default spaces. Well, the method returning random spaces also mixes them up with default spaces just for the fun of it.

After that I moved on to creating a test for the opposite functionality (non-defaults to defaults) and a real method to make it work.

Now I had to find a way to get from having a file to having the contents of that file as a string. So my next assumption was that I will have an IO class. Like the rest of my classes in this project - it should do very little. It needs to getFileAsString and it also needs to saveStringAsFile. That’s where I reasoned that using a BufferedReader or a BufferedWriter or even going more low-level would be out of scope. So both methods use Apache’s FileUtils that can helpfully read a file to string and write a string to a file. Awesome. Still, I felt that a few unit tests could do some good here. So shift your gaze to exhibit ‘I’, or should I say… exhibit IOTest. Ugh, I’m sorry, I regret this joke already…

But yeah, the tests are pretty straightforward: check that the getFileAsString method returns the file contents as a string; then test that it doesn’t blow up if the specified path does not exist; test that saving a string as a file works; then make sure nothing goes wrong if somehow the string we’re trying to save ended up being null; and also, while we’re at it, make sure that nothing will go wrong if both the string and the path that we’re trying to save to are null.

Once that was done and tested - the last step was to provide some sort of a way to interact with the program. Like, pass it filepaths and everything. Since command line interactions are all the rage these days (Caution: command line interactions may not be all the rage these days) - I’ve decided to go for that. Again, it didn’t feel in the scope of this project to actually create the logic for parsing command line arguments, so I used a neat little library from Apache (CLI) that does all of that magic for you.

Incidentally the SpaceProgram class is the only one that doesn’t have tests (yes, I’m not counting Whitespace). There are two reasons for that: I started running out of time; I’m not convinced there’s a good way to test command line stuff (I’m not religious in this belief though, if I find a good way to test it - it’ll be my new favourite thing).

Space Program is straightforward. It has the main entry point where it parses the passed in arguments and, based on what the user wants, either calls replaceWithDefaults or replaceWithRandomSpaces. There’s a little bit of code duplication happening in those two methods, we can do better. There is also a method that constructs the command line options that we’re looking for when parsing args. Easy-peasy.

Things to note

Space Program has exactly zero comments. I believe variables, methods and classes I’ve created are well-enough named to be self-explanatory. Do let me know if I’m delusional.

This program probably won’t work as inconspicuously as I’d like it to. As in, because the whitespaces we’re using are so fancy - I bet in the majority of IDEs they’ll show up as unrecognized symbols. Which is sucky, but, on the plus side - you can easily fork the repository, tweak it a bit and make it do what you want, like have it replace punctuation with your favourite set of emojis or something. Or, if you’re feeling extra evil, have the program insert zero width no-break spaces, zero width spaces and Mongolian vowel separators in random places of the user-provided file. Be evil.

Update: It has been pointed out to me regarding my previous post that unit testing philosophy is about only testing one thing in a unit test, when I actually had several assert statements per unit test. That is, of course, correct, and the only reason I did it that way was because I was being lazy. Thanks for pointing it out, it is now on my glassboard as part of the list to help me get better at life.