Skip to content

The volume of the holes, covered by water in the rainy day

License

Notifications You must be signed in to change notification settings

t-izbassar/rainy-day

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rainy Day

Build Status codecov

Contents

Overall description

It's a repository with source codes for application, that exposes the REST-endpoints via Java EE technologies. Those endpoints are used to calculate volume of the holes, that will be covered by the rain.

Imagine the rainy day and the uneven surface. If the day will be dull enough the holes of that surface will be covered with the water. And it's very important to determine the amount of volume, that water have took. That's when this application comes into play.

Implementation idea and complexity

Suppose we have a surface, with hills values as follows:

final List<Integer> hills = Arrays.asList(3, 2, 4, 1, 2);

The idea is to compare the highest values from left perspective (meaning that we are going from start to end) to the highest values from right perspective (going from end to start), which in my program represented by LeftSurface.java and by RightSurface.java.

For the given above surface they will produce character of it as follows:

// will produce 3, 3, 4, 4, 4
final List<Integer> left = new LeftSurface(hills).character();
// will produce 4, 4, 4, 2, 2
final List<Integer> right = new RightSurface(hills).character();

We are doing so, because we need to know the relevant high values between hills. Having those high values now we can calculate the volume that is covered by rain (check ActualVolume.java):

for (int index = 0; index < hills.size(); index += 1) {
    final int min = Math.min(left.get(index), right.get(index));
    final int hill = hills.get(index);
    if (hill < min) {
        result += min - hill;
    }
}

So, the complexity is O(n) as we are doing the linear iteration operation over the given hills. We do it three times and we are using additional space for left and right character of the surface (so we need triple as much space as the given hills).

The algorithm is exposed via REST-service, which is accepting the POST request to rainy-day/hills endpoint (check HillsEndpoint.java). The body of the request should be json as follows (check HillsDto.java):

{
  "hills": [4, 3, 2, 1, 3]
}

The endpoint will return the answer in the answer body. The good practice is to return the redirect address with the created resource. Something like this: GET rainy-day/hills/id. But in my case I decided to return it straight in the answer body, as it is also used to return the updated resource. In our case, we don't do any operations, that affects the state. I decided not to use GET for that resource, as it is hard to provide the hills array using GET. The usage of request body is not recommended for GET requests, so I decided to go with POST.

Technologies

For testing purposes I used JUnit4, Hamcrest, which are enough for unit tests. I haven't brought Mockito as I don't have any mocks, but that what I would choose, If I had to.

For integration tests I used docker-maven-plugin, which runs the docker container with WildFly and deployed war application in it. As http client I used jcabi-http as it provides fluent API and supports assertions on REST responses out of the box.

How to build and run

The application is built by providing the rainy-day.war, which can be deployed on any Java EE 7 compliant server (the WildFly is one that is tested though). To build deployable war, simply use

mvn package

For basic verification builds (Unit tests, coverage, static-analysis):

mvn verify

For integration tests it's required to have docker installed. The maven-docker-plugin will try to find relevant docker installation on your machine. Thus it is required to run it as administrator. To run full set of checks, that are performed on CI, (with 8080 port - choose any free one) run:

sudo env "PATH=$PATH" mvn verify -P integration-test \ 
-Ddocker.local.port=8080

Also, there is Dockerfile, that you can use to start WildFly instance. It's used only for local development and is not equal to the CI one.

Principles

I think that the main challenge when writing software is managing the complexity of the sources. I would like to keep complexity as low as possible and in order to do that I'm trying to follow those principles:

  • I think, that projects should have continuous integration environment, that checks the sources for every merged change. This is important, as this will help to caught mistakes as fast as possible. Of course it's depends of the quality of the build process. Also the proposed changes (via PRs) should also be checked.
  • I think, that projects should have static analysis rules from the day 0. The stricter they are - the better, as this will allow everyone to follow the same guidelines, which is very valuable in long-term projects.
  • I think it's better to have end-to-end functional tests, that will allow to safely refactor implementation details, than 100% coverage with unit tests (which is still valuable).
  • I think we should limit the amount of mutable classes and control the changes to the state. The most tricky part is to control changes to some shared state. Managing immutable objects is much more easier and that what we should try to use more.
  • I think we should carefully control the boundaries of the classes and overall coupling of the components and layers. That is the most hardest thing to achieve (from my personal feeling). It's very easy to inject something you need straight to the point where you need it instead of evaluating the abstractions and managing coupling carefully.
  • I think that application should fail as fast as possible. The more explicit the failure is - the better, as it will allow to catch mistake earlier and in the right place. Instead of swallowing the exception, we should throw it. Instead of returning null or 0 for invalid case it's better to throw exception and let application to fail, as this will lead to discovering the root cause of the problem, rather than having to do workarounds and wondering on how we received null in that particular place.

Of course, there is no ideal software and no ideal projects. Sometimes we need to make certain dirty decisions in order to get things done. Sometimes we need to introduce workaround, instead of strategic solution to the problem. But those decisions should be made with the consequences in mind, as they will follow us, until we really fix the issue.

Final thoughts

Thanks for your time. Overall I don't like the algorithm based tasks as they don't showcase the important questions of writing maintainable and clean code. Of course you can try to do it as clean as possible and that's what I've tried to do here. It's certainly not ideal, so I'm looking forward to constructive comments, from which I can learn. Hope, I've demonstrated my thought process and it will be a good starting point to get to know each other. In the end, it all comes down to the collaboration process with people, so it's not so important what exactly the technology was or what exactly the algorithm was. The important thing is to share the same values and to try to achieve the same goals.

License

The code is under MIT License. Enjoy.

About

The volume of the holes, covered by water in the rainy day

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages