-
Notifications
You must be signed in to change notification settings - Fork 4
Stages 2,3,4,5 #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
kforkartoshka
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hyperskill review
Good use of try-with-resources, but missed socket in Server.
Ok overall, the main issue is only with one-at-a-time connections
| try (ServerSocket server = new ServerSocket(PORT)) { | ||
| while (true) { | ||
| Session session = new Session(server.accept()); | ||
| session.run(); // it does not block this thread |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only it does block it. Well, not really blocks, cause execution still continues, but it only serves one client.
You have another infinite loop in Session::run. So by calling it inside the first infinite loop, its iteration won't end until a client disconnects.
You can fix it by making Session into Thread
| } | ||
|
|
||
| class Session { | ||
| private final Socket socket; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Socket is never closed
|
|
||
| System.out.println("Recieved: " + msg); | ||
| output.writeUTF(msg); | ||
| System.out.println("Sent: " + msg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong input and output messages format.
You only send integers, but the task is to send "Give me a record # N" from Client and "A record # N was sent!" from Server
|
|
||
| output.writeUTF(msg); // sending message to the server | ||
| System.out.println("Sent: " + msg); | ||
| String receivedMsg = input.readUTF(); // response message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant comments, it's clear that you're sending and receiving something from the method names
kforkartoshka
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's OK but could be simplified and decoupled more
Try to write methods with single-responsibility in mind – each method does one thing. Now your main method does everything - parameters validation, client connection, command handling, reading and writing to socket
It's way harder to read such code and so you start to rely on comments that explain obvious stuff. But if you decouple program behavior, you'll find it easier to reason with.
| DataInputStream input = new DataInputStream(socket.getInputStream()); | ||
| DataOutputStream output = new DataOutputStream(socket.getOutputStream()) | ||
| ) { | ||
| if (client.command.equals("set")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Commands validator you use value.trim().toLowerCase(), but here you don't preprocess command, can lead to a potential bug
Also, when checking strings against hardcoded values, better to switch their places, e.g. "set".equals(client.command), or use Objects.equals(client.command, "set"), to avoid NullPointerException
Another approach would be to use switch, e.g. switch (client.command.trim().toLowerCase()) { case "get": ... }
| output.writeUTF(client.command + " " + client.index); | ||
| System.out.println("Sent: " + client.command + " " + client.index); | ||
| } | ||
| String receivedMsg = input.readUTF(); // response message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant comment
You can just name the variable response
| System.out.println("Sent: " + msg); | ||
|
|
||
| int portNumber = Integer.parseInt(args[0]); | ||
| Protocol protocol = new Protocol(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protocol class is missing from the source code
| DataOutputStream output = new DataOutputStream(socket.getOutputStream()) | ||
| ) { | ||
| String inputLine, outputLine; | ||
| inputLine = input.readUTF(); // reading a message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant comments duplicating method names
| public class Commands implements IParameterValidator { | ||
| public void validate(String name, String value) | ||
| throws ParameterException { | ||
| value = value.trim().toLowerCase(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bad practice to reassign input parameter
Better treat and mark them as final
More and more languages do that by default
| public void validate(String name, String value) | ||
| throws ParameterException { | ||
| value = value.trim().toLowerCase(); | ||
| if (!(value.equals("set") || value.equals("get") || value.equals("delete"))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine, except for potential NPE, which I described in Client class
But could be simplified by creating a Set of allowed commands and checking if passed value is in this set
The set could also be used in exception message to create a string of allowed commands allowed.stream().collect(Collectors.joining("/"))
kforkartoshka
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok overall, but try to separate logical parts more (with new lines or by extracting into separate methods)
Cause now your flow looks like this – createrequestparseittojsonsenditlogitreceiveresponselogit
It's just one indistinguishable blob of words
| import java.util.Objects; | ||
|
|
||
| public class Request { | ||
| public enum TYPE {SET, GET, DELETE} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now it's okay, cause you use it only inside the Request class, but if you'd try to pass TYPE as a parameter somewhere else, you'll have to create an instance of 'Request' to access the enum. To avoid it make it static
Also, enum is just another class, and so follows the same naming convention (upper camel case, instead of all-caps). The values of enum are constants, and so they are written in caps, but not the enum itself
| package org.hyperskill.database.common; | ||
|
|
||
| public class Response { | ||
| public enum TYPE {OK, FAIL} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here as in Request
| import java.util.Objects; | ||
|
|
||
| public class JsonProtocol { | ||
| JsonStorage storage = new JsonStorage(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nicer and cleaner to introduce inversion of control here by creating and using Storage interface instead of concrete implementation
| import com.beust.jcommander.IParameterValidator; | ||
| import com.beust.jcommander.ParameterException; | ||
|
|
||
| public class Commands implements IParameterValidator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better name it CommandValidator, cause just Commands is ambiguous
The name of a class should reflect on what it does
| import com.beust.jcommander.IParameterValidator; | ||
| import com.beust.jcommander.ParameterException; | ||
|
|
||
| public class PositiveInteger implements IParameterValidator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same with naming as with Commands
kforkartoshka
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly okay again, with the same problems about code readability and structure as in previous stages
Nice thought of input parameters validation in methods
| String command = null; | ||
| String key = null; | ||
| StringBuilder value = new StringBuilder(); | ||
| try (InputStream in = Files.newInputStream(file); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole thing could have been implemented in a couple of lines, using Files.lines(file)
But if you don't know about streams, it's ok
| import java.nio.file.Paths; | ||
| import java.util.Objects; | ||
|
|
||
| public class Util { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating an "utils" class is tempting but actually is a bad practice. It tempts you to put unrelated stuff together into one class
Here you do IO operations, so the name should convey this
| } | ||
| */ | ||
| } | ||
| /* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't commit commented-out code
It's just a noise
If you want to preserve it for potential future reference – git already did it for you, you can check file history for that
| return value; | ||
| } | ||
|
|
||
| public static TYPE validateCommand(String word) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not validation but parsing
Also, it could be done with Enum::valueOf method, which will also throw IllegalArgumentException on unknown parameter
| public enum TYPE {OK, FAIL} | ||
|
|
||
| public boolean isOk() { | ||
| if (this.type == TYPE.OK) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, when your method returns boolean, you can just return boolean expression used in if clause
| return false; | ||
| } | ||
|
|
||
| public TYPE getType() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better have class parameters at the top of its definition. This way you and everyone else can clearly see what this class is constructed of
Your whole structure here is upside-down
It usually goes as follows:
– parameters
– code blocks
– constructors
– getters/setters
– methods
– utility
| storage = JsonStorage.getINSTANCE(); | ||
| } | ||
|
|
||
| public void run() { // run the service |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, what's the purpose of such comments?
It's like writing import java.net.Socket; //import Socket class from java.net package
| writeLock = lock.writeLock(); | ||
| } | ||
|
|
||
| public static JsonStorage getINSTANCE() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to caps "instance" in getter name, it still follows camel-case in this situation
| private String value; | ||
|
|
||
| public Request(String type, String key, String value) { | ||
| public Request(String type, String key, String value) throws IllegalArgumentException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use TYPE enum as type parameter type instead of String
Usage of String here only introduces potential failure and complicates logic where you additionally need to parse it
| import java.util.concurrent.ExecutorService; | ||
| import java.util.concurrent.Executors; | ||
|
|
||
| class NetworkService implements Runnable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why making it Runnable if you're not using it as runnable
Calling nws.run(); in main doesn't execute it in a separate thread. You need to call start for that
No description provided.