Skip to content

Conversation

@kostylevv
Copy link

No description provided.

@kostylevv kostylevv changed the title Stage 2 Stage 2,3 Jan 8, 2019
@kostylevv kostylevv changed the title Stage 2,3 Stages 2,3 Jan 8, 2019
@kostylevv kostylevv changed the title Stages 2,3 Stages 2,3,4 Jan 10, 2019
@kostylevv kostylevv changed the title Stages 2,3,4 Stages 2,3,4,5 Jan 29, 2019
Copy link

@kforkartoshka kforkartoshka left a 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

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;

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);

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

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

Copy link

@kforkartoshka kforkartoshka left a 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")) {

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

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();

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

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();

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"))) {

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("/"))

Copy link

@kforkartoshka kforkartoshka left a 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}

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}

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();

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 {

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 {

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

Copy link

@kforkartoshka kforkartoshka left a 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);

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 {

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

}
*/
}
/*

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) {

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) {

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() {

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

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() {

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 {

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 {

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants