Embracing the-power-of-refactor

107
Embracing the Power of Refactor REN Xiaojun

Transcript of Embracing the-power-of-refactor

Embracing the Power of Refactor

REN Xiaojun

Code smells are heuristics for refactoring

Our design communicates to us through resistance.

Code is difficult to test

Code is difficult to change

Code is difficult to reuse

This resistance is valuable feedback

Code smells are hints from our software about how to reduce this resistance.

This is one way our design emerges.

Commands

PING

responds with PONG

SEND

send messages to GCM

Queue<String> queue = new LinkedBlockingQueue<>(); HttpClient httpclient = HttpClients.createDefault(); DatagramSocket socket = new DatagramSocket(6889);

Runnable pusher = new Runnable() { @Override public void run() { while (true) { String json = queue.poll(); if (json == null || json.length() <= 0) { try { sleep(1000); continue; } catch (InterruptedException e) { e.printStackTrace(); } } HttpPost post = new HttpPost("https://android.googleapis.com/gcm/send"); post.setHeader("Authorization", "key=AIzaSyCABSTd47XeIH"); post.setHeader("Content-Type", "application/json"); System.out.println("posting " + json); try { HttpEntity entity = new StringEntity(json); post.setEntity(entity);

httpclient.execute(post); } catch (Exception e) { e.printStackTrace(); } } } };

Thread t = new Thread(pusher); t.start();

request creation & delivery

while (true) { try { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); String data = new String(received.getData()); String command = data.split("\\s")[0];

if ("PING".equals(command)) { byte[] sendData = "PONG".getBytes(); DatagramPacket sendPacket = new DatagramPacket(…); socket.send(sendPacket); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = … queue.add(json); } } } catch (IOException e) { e.printStackTrace(); } } }

command dispatch

parameter extraction

smell:Long Method

recipe:Replace Method With Method Object

PushDaemon

public class PushDaemon {

private final Queue<String> queue; private final HttpClient httpclient; private final DatagramSocket socket;

public PushDaemon() throws SocketException { queue = new LinkedBlockingQueue<>(); httpClient = HttpClients.createDefault(); socket = new DatagramSocket(6889);

} }

public void start() { Runnable client = new Runnable() { @Override public void run() { while (true) { String json = queue.poll(); if (json == null || json.length() <= 0) { try { sleep(1000); continue; } catch (InterruptedException e) { e.printStackTrace(); } } HttpPost post = new HttpPost("https://android.googleapis.com/gcm/send"); post.setHeader("Authorization", "key=AIzaSyCABSTd47XeIH"); post.setHeader("Content-Type", "application/json"); System.out.println("posting " + json); try { HttpEntity entity = new StringEntity(json); post.setEntity(entity);

httpclient.execute(post); } catch (Exception e) { e.printStackTrace(); } } } };

Thread t = new Thread(client); t.start();

while (true) { try { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); String data = new String(received.getData()); String command = data.split("\\s")[0];

if ("PING".equals(command)) { byte[] sendData = "PONG".getBytes(); DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, received.getAddress(), received.getPort()); socket.send(sendPacket); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; System.out.println(json); queue.add(json); } } } catch (IOException e) { e.printStackTrace(); } } } }

recipe:Extract Method

public void start() { Runnable client = new Runnable() { @Override public void run() { while (true) { String json = queue.poll(); if (json == null || json.length() <= 0) { try { sleep(1000); continue; } catch (InterruptedException e) { e.printStackTrace(); } } HttpPost post = new HttpPost("https://android.googleapis.com/gcm/send"); post.setHeader("Authorization", "key=AIzaSyCABSTd47XeIH"); post.setHeader("Content-Type", "application/json"); System.out.println("posting " + json); try { HttpEntity entity = new StringEntity(json); post.setEntity(entity);

httpclient.execute(post); } catch (Exception e) { e.printStackTrace(); } } } };

Thread t = new Thread(client); t.start();

while (true) { try { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); String data = new String(received.getData()); String command = data.split("\\s")[0];

if ("PING".equals(command)) { byte[] sendData = "PONG".getBytes(); DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, received.getAddress(), received.getPort()); socket.send(sendPacket); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; System.out.println(json); queue.add(json); } } } catch (IOException e) { e.printStackTrace(); } } } }

spawn workers

process requests

public void start() { spawnWorkers(); while(true){ processRequests(); } }

private void spawnWorkers() { Runnable client = new Runnable() { @Override public void run() while (true) { String json = queue.poll(); if (json == null || json.length() <= 0) {

try { sleep(1000); continue; } catch (InterruptedException e) { e.printStackTrace(); } } HttpPost post = new HttpPost("https://android.googleapis.com/gcm/send"); post.setHeader("Authorization", "key=AIzaSyCABSTd47XeIH"); post.setHeader("Content-Type", "application/json"); try { HttpEntity entity = new StringEntity(json); post.setEntity(entity); httpclient.execute(post); } catch (Exception e) { e.printStackTrace(); } } } };

Thread t = new Thread(client); t.start(); } }

private void processRequests() { try { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); String data = new String(received.getData()); String command = data.split("\\s")[0];

if ("PING".equals(command)) { byte[] sendData = "PONG".getBytes();

DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, received.getAddress(), received.getPort());

socket.send(sendPacket); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; queue.add(json);

} }

} catch (IOException e) { e.printStackTrace();

} }

public class PushDaemon {

private final Queue<String> queue; private final HttpClient httpclient; private final DatagramSocket socket;

public PushDaemon() throws SocketException { queue = new LinkedBlockingQueue<>(); httpclient = HttpClients.createDefault(); socket = new DatagramSocket(6889);

}

public void start() { spawnWorkers(); while(true){processRequests();} }

private void processRequests() { //… }

private void spawnWorkers() { //… }

}

Update authorization key

Increase thread pool size

Swap HTTP client

Use a different transport protocol

Modify wire protocol format

Add commands

Add a different push notification service

Move UDP port

Use x-www-urlencoded instead of JSON

Lower maximum payload size

Bind a specific interface address

Update push service URL

smell:Divergent Change

recipe:Extract Class

private void spawnWorkers() { Runnable client = new Runnable() { @Override public void run() while (true) { String json = queue.poll(); if (json == null || json.length() <= 0) {

try { sleep(1000); continue; } catch (InterruptedException e) { e.printStackTrace(); } } HttpPost post = new HttpPost("https://android.googleapis.com/gcm/send"); post.setHeader("Authorization", "key=AIzaSyCABSTd47XeIH"); post.setHeader("Content-Type", "application/json"); try { HttpEntity entity = new StringEntity(json); post.setEntity(entity); httpclient.execute(post); } catch (Exception e) { e.printStackTrace(); } } } };

Thread t = new Thread(client); t.start(); } }

public class Worker { private final Queue<String> queue; private final HttpClient httpclient;

public Worker() { queue = new LinkedBlockingQueue<>(); httpclient = HttpClients.createDefault(); }

public void add(String json) { this.queue.add(json); }

}

public class PushDaemon {

public PushDaemon() throws SocketException { queue = new LinkedBlockingQueue<>(); httpclient = HttpClients.createDefault(); socket = new DatagramSocket(6889);

} }

public class PushDaemon {

public PushDaemon() throws SocketException { worker = new Worker(); socket = new DatagramSocket(6889);

} }

private void processRequests() { try { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); String data = new String(received.getData()); String command = data.split("\\s")[0];

if ("PING".equals(command)) { byte[] sendData = "PONG".getBytes();

DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, received.getAddress(), received.getPort());

socket.send(sendPacket); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; queue.add(json);

} }

} catch (IOException e) { e.printStackTrace();

} }

private void processRequests() { try { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); String data = new String(received.getData()); String command = data.split("\\s")[0];

if ("PING".equals(command)) { byte[] sendData = "PONG".getBytes();

DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, received.getAddress(), received.getPort());

socket.send(sendPacket); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; worker.add(json);

} }

} catch (IOException e) { e.printStackTrace();

} }

public class PushDaemon { public void start() {

spawnWorkers(); while(true){processRequests();} } }

public class PushDaemon { public void start() {

worker.spawn(); while(true){processRequests();} } }

public class PushDaemon {

public PushDaemon() throws SocketException { worker = new Worker();

socket = new DatagramSocket(6889); } }

private void processRequests() { while (true) { try { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); String data = new String(received.getData()); String command = data.split("\\s")[0];

if ("PING".equals(command)) { byte[] sendData = "PONG".getBytes();

DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, received.getAddress(), received.getPort());

socket.send(sendPacket); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; worker.add(json);

} }

} catch (IOException e) { e.printStackTrace();

} }

}

public class UDPServer {

private final DatagramSocket socket;

public UDPServer() throws SocketException { socket = new DatagramSocket(6889); } }

private void processRequests() { while (true) { try { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); String data = new String(received.getData()); String command = data.split("\\s")[0];

if ("PING".equals(command)) { byte[] sendData = "PONG".getBytes();

DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, received.getAddress(), received.getPort());

socket.send(sendPacket); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; worker.add(json);

} }

} catch (IOException e) { e.printStackTrace();

} }

}

receive

send

public class UDPServer {

private final DatagramSocket socket;

public UDPServer() throws SocketException { socket = new DatagramSocket(6889); }

public DatagramPacket receive() throws IOException { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); return received; }

public void send(String message, InetAddress address, int port) throws IOException { byte[] sendData = message.getBytes(); DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, port); socket.send(sendPacket); } }

private void processRequests() { try { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); String data = new String(received.getData()); String command = data.split("\\s")[0];

if ("PING".equals(command)) { byte[] sendData = "PONG".getBytes();

DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, received.getAddress(), received.getPort());

socket.send(sendPacket); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; worker.add(json);

} }

} catch (IOException e) { e.printStackTrace();

} }

public class PushDaemon {

private final Worker worker; private final UDPServer server;

public PushDaemon() throws SocketException { worker = new Worker(); server = new UDPServer();

}

public void start() { worker.spawn(); while(true){processRequests();} }

private void processRequests() { //… } }

private void processRequests() { try { DatagramPacket received = server.receive(); String data = new String(received.getData());

String command = data.split("\\s")[0]; if ("PING".equals(command)) { server.send("PONG", received.getAddress(), received.getPort());

} else if ("SEND".equals(command)) { String message = data.replace(command, "").trim();

Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; worker.add(json); } } } catch (IOException e) { e.printStackTrace(); } }

smell: Inappropriate Intimacy

public class PushDaemon {

private final Worker worker; private final UDPServer server;

public PushDaemon() throws SocketException { worker = new Worker(); server = new UDPServer();

}

public void start() { worker.spawn(); processRequests(); }

private void processRequests() { //… } }

public class PushDaemon {

private final Worker worker; private final UDPServer server;

public PushDaemon() throws SocketException { worker = new Worker(); server = new UDPServer(this);

}

public void start() { worker.spawn(); processRequests(); }

private void processRequests() { //… } }

public class PushDaemon {

private final Worker worker; private final UDPServer server;

public PushDaemon() throws SocketException { worker = new Worker(); server = new UDPServer(this);

}

public void start() { worker.spawn(); while(true){processRequests();} }

private void processRequests() { //… } }

public class PushDaemon {

private final Worker worker; private final UDPServer server;

public PushDaemon() throws SocketException { worker = new Worker(); server = new UDPServer(this);

}

public void start() { worker.spawn(); server.listen(6889); }

private void processRequests() { //… } }

private void processRequests() { try { DatagramPacket received = server.receive(); String data = new String(received.getData());

String command = data.split("\\s")[0]; if ("PING".equals(command)) { server.send("PONG", received.getAddress(), received.getPort());

} else if ("SEND".equals(command)) { String message = data.replace(command, "").trim();

Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; worker.add(json); } } } catch (IOException e) { e.printStackTrace(); } }

private void processRequests(DatagramPacket received) { try { String data = new String(received.getData());

String command = data.split("\\s")[0]; if ("PING".equals(command)) { server.send("PONG", received.getAddress(), received.getPort());

} else if ("SEND".equals(command)) { String message = data.replace(command, "").trim();

Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; worker.add(json); } } } catch (IOException e) { e.printStackTrace(); } }

private void call(DatagramPacket received) { try { String data = new String(received.getData());

String command = data.split("\\s")[0]; if ("PING".equals(command)) { server.send("PONG", received.getAddress(), received.getPort());

} else if ("SEND".equals(command)) { String message = data.replace(command, "").trim();

Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; worker.add(json); } } } catch (IOException e) { e.printStackTrace(); } }

public class UDPServer {

public UDPServer(PushDaemon app) { this.app = app; }

public void listen(int port) throws IOException { socket = new DatagramSocket(port); while (true) { app.call(receive()); } }

public DatagramPacket receive() throws IOException { byte[] buf = new byte[4096]; DatagramPacket received = new DatagramPacket(buf, buf.length); socket.receive(received); return received; }

public void send(String message, InetAddress address, int port) throws IOException { byte[] sendData = message.getBytes(); DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, port); socket.send(sendPacket); } }

public class PushDaemon { public PushDaemon() throws SocketException { worker = new Worker(); server = new UDPServer(this); } public void start() throws IOException { worker.spawn(); server.listen(6889); }

public void call(DatagramPacket received) { try { String data = new String(received.getData());

String command = data.split("\\s")[0]; if ("PING".equals(command)) { server.send("PONG", received.getAddress(), received.getPort()); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; System.out.println(json); worker.add(json); } } } catch (IOException e) { e.printStackTrace(); } }

}

smell: Case Statement

recipe: Replace Conditional with Polymorphism

public void call(DatagramPacket received) { try { String data = new String(received.getData());

String command = data.split("\\s")[0]; if ("PING".equals(command)) { server.send("PONG", received.getAddress(), received.getPort()); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; System.out.println(json); worker.add(json); } } } catch (IOException e) { e.printStackTrace(); } }

}

public class Ping { private UDPServer server; private DatagramPacket received;

public Ping(UDPServer server, DatagramPacket received) { this.server = server; this.received = received; }

public void run() throws IOException { server.send("PONG", received.getAddress(), received.getPort()); } }

public void call(DatagramPacket received) { try { String data = new String(received.getData());

String command = data.split("\\s")[0]; if ("PING".equals(command)) { new Jobs(server, received).run(); } else if ("SEND".equals(command)) { String message = data.replace(command, "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; worker.add(json); } } } catch (IOException e) { e.printStackTrace(); } }

}

public class Send {

private DatagramPacket received;

public Send(DatagramPacket received) { this.received = received; }

public String run() { String data = new String(received.getData()); String message = data.replace("SEND", "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { return "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; } return null; }

}

public void call(DatagramPacket received) { try { String data = new String(received.getData());

String command = data.split("\\s")[0]; if ("PING".equals(command)) { new Ping(server, received).run(); } else if ("SEND".equals(command)) {

String json = new Send(received).run(); worker.add(json);

} } catch (IOException e) { e.printStackTrace(); } }

}

public class Worker { private final Queue<String> queue; private final HttpClient httpclient;

public Worker() { queue = new LinkedBlockingQueue<>(); httpclient = HttpClients.createDefault(); }

public void add(String json) { this.queue.add(json); }

void spawn() { Runnable client = new Runnable() { @Override public void run() { while (true) { String json = queue.poll(); if (json == null || json.length() <= 0) { try { sleep(1000); continue; } catch (InterruptedException e) { e.printStackTrace(); } } HttpPost post = new HttpPost("https://android.googleapis.com/gcm/send"); post.setHeader("Authorization", "key=AIzaSyCABSTd47XeIH"); post.setHeader("Content-Type", "application/json"); System.out.println("posting " + json); try { HttpEntity entity = new StringEntity(json); post.setEntity(entity);

httpclient.execute(post); } catch (Exception e) { e.printStackTrace(); } } } };

Thread t = new Thread(client); t.start(); } }

public class Send {

private DatagramPacket received; private final HttpClient httpclient;

public Send(DatagramPacket received) { this.received = received; this.httpclient = HttpClients.createDefault(); }

public void run() throws IOException { String data = new String(received.getData()); String message = data.replace("SEND", "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; HttpPost post = new HttpPost("https://android.googleapis.com/gcm/send"); post.setHeader("Authorization", "key=AIzaSyCABSTd47XeIH"); post.setHeader("Content-Type", "application/json");

HttpEntity entity = new StringEntity(json); post.setEntity(entity);

httpclient.execute(post); } } }

public class Worker { public Worker() { queue = new LinkedBlockingQueue<>(); } public void accept(Job job) { this.queue.add(job); } void spawn() { Runnable client = new Runnable() { @Override public void run() { while (true) { Job job = queue.poll(); if (job != null) { try { job.run(); } catch (IOException e) { e.printStackTrace(); } } } } };

Thread t = new Thread(client); t.start(); } }

public interface Job { void run() throws IOException; }

public class Ping implements Job { //… }

public class Send implements Job { //… }

public class PushDaemon {

private final Worker worker; private final UDPServer server;

public PushDaemon() throws SocketException { worker = new Worker(); server = new UDPServer(this); }

public void start() throws IOException { worker.spawn(); server.listen(6889); }

public void call(DatagramPacket received) { String data = new String(received.getData()); String command = data.split("\\s")[0]; Job job = null; if ("PING".equals(command)) { job = new Ping(server, received); } else if ("SEND".equals(command)) { job = new Send(received); } if(job != null) { worker.accept(job); } }

}

Recipe:Move Creation Knowledge to Factory

public interface Job { public static Job create(DatagramPacket received, UDPServer server) { String data = new String(received.getData()); String command = data.split("\\s")[0]; Job job = null; if ("PING".equals(command)) { job = new Ping(server, received); } else if ("SEND".equals(command)) { job = new Send(received); } return job; } }

public class PushDaemon {

private final Worker worker; private final UDPServer server;

public PushDaemon() throws SocketException { worker = new Worker(); server = new UDPServer(this); }

public void start() throws IOException { worker.spawn(); server.listen(6889); }

public void call(DatagramPacket received) { Job job = Job.create(received, server); if (job != null) { worker.accept(job); } } }

public class Ping implements Job { public void run() throws IOException { server.send("PONG", received.getAddress(), received.getPort()); } }

public class Send implements Job { public void run() throws IOException { String data = new String(received.getData()); String message = data.replace("SEND", "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; HttpPost post = new HttpPost("https://android.googleapis.com/gcm/send"); post.setHeader("Authorization", "key=AIzaSyCABSTd47XeIH"); post.setHeader("Content-Type", "application/json");

HttpEntity entity = new StringEntity(json); post.setEntity(entity);

httpclient.execute(post); } } }

public class Client { private final InetSocketAddress socketAddress;

public Client(InetSocketAddress socketAddress) { this.socketAddress = socketAddress; }

public int port() { return socketAddress.getPort(); }

public InetAddress address() { return socketAddress.getAddress(); } }

public class UDPServer {

public UDPServer(PushDaemon app) { this.app = app; }

public void listen(int port) throws IOException { socket = new DatagramSocket(port); while (true) { app.call(receive()); } } }

public class UDPServer { public void listen(int port) throws IOException { socket = new DatagramSocket(port); while (true) { DatagramPacket received = receive(); String message = messageOf(received); Client client = clientOf(received); app.call(message, client); } }

private String messageOf(DatagramPacket received) { return new String(received.getData()); }

private Client clientOf(DatagramPacket received) { return new Client((InetSocketAddress) received.getSocketAddress()); } }

public interface Job { public static Job create(DatagramPacket received, UDPServer server) { String data = new String(received.getData()); String command = data.split("\\s")[0]; Job job = null; if ("PING".equals(command)) { job = new Ping(server, received); } else if ("SEND".equals(command)) { job = new Send(received); } return job; } }

public interface Job { public static Job create(Client client, String message, UDPServer server) {

//… } }

public class Ping implements Job { private final Client client; private final String message; private UDPServer server;

public Ping(Client client, String message, UDPServer server) { this.client = client; this.message = message; this.server = server;

}

public void run() throws IOException { server.send("PONG", client.address(), client.port()); } }

smell:Feature Envy

Extracted objects tend to attract behaviour.

public class Client { private final InetSocketAddress socketAddress; private final UDPServer server;

public Client(InetSocketAddress socketAddress, UDPServer server) { this.socketAddress = socketAddress; this.server = server; }

public int port() { return socketAddress.getPort(); }

public InetAddress address() { return socketAddress.getAddress(); }

public void send(String message) throws IOException { server.send(message, address(), port()); }

}

public class Ping implements Job { private final Client client; private final String message; private UDPServer server;

public Ping(Client client, String message, UDPServer server) { this.client = client; this.message = message; this.server = server;

}

public void run() throws IOException { server.send("PONG", client.address(), client.port()); } }

public class Client { private final InetSocketAddress socketAddress; private final UDPServer server;

public Client(InetSocketAddress socketAddress, UDPServer server) { this.socketAddress = socketAddress; this.server = server; }

public int port() { return socketAddress.getPort(); }

public InetAddress address() { return socketAddress.getAddress(); }

public void send(String message) throws IOException { server.send(message, address(), port()); }

}

public class Ping implements Job { public void run() throws IOException { client.send("PONG"); } }

public class Send implements Job {

public Send(Client client, String message) { this.client = client; this.message = message; this.server = server; this.httpclient = HttpClients.createDefault(); }

@Override public void run() throws IOException { String message = this.message.replace("SEND", "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { String json = "{\"registration_ids\" : \"" + matcher.group(1) + "\", \"data\" : { \"alert\" : \"" + matcher.group(2) + "\"}}"; HttpPost post = new HttpPost("https://android.googleapis.com/gcm/send"); post.setHeader("Authorization", "key=AIzaSyCABSTd47XeIH"); post.setHeader("Content-Type", "application/json");

HttpEntity entity = new StringEntity(json); post.setEntity(entity);

httpclient.execute(post); } } }

PushNotification

public class PushNotification { public PushNotification(String registrationId, String alert) { this.registrationId = registrationId; this.alert = alert; }

public void deliver() throws IOException { HttpPost post = new HttpPost("https://android.googleapis.com/gcm/send"); post.setHeader("Authorization", "key=AIzaSyCABSTd47XeIH"); post.setHeader("Content-Type", "application/json");

HttpEntity entity = new StringEntity(toJson()); post.setEntity(entity);

httpclient.execute(post); }

private String toJson() { return "{\"registration_ids\" : \"" + registrationId + "\", \"data\" : { \"alert\" : \"" + alert + "\"}}"; } }

public class Send implements Job {

private final String message;

public Send(String message) { this.message = message; }

@Override public void run() throws IOException { String message = this.message.replace("SEND", "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { new PushNotification(matcher.group(1), matcher.group(2)).deliver(); } } }

smell:Primitive Obsession

we’re using simple data types to represent complex ideas

public class Send implements Job {

private final String message;

public Send(String message) { this.message = message; }

@Override public void run() throws IOException { String message = this.message.replace("SEND", "").trim(); Pattern p = Pattern.compile("([a-zA-Z0-9_-]*) \"([^\"]*)\""); Matcher matcher = p.matcher(message);

if (matcher.matches()) { new PushNotification(matcher.group(1), matcher.group(2)).deliver(); } } }

COMMAND [parameters]

parameter “second parameter”

public class Request {

private static Pattern PATTERN = Pattern.compile("([SEND|PING]) ([a-zA-Z0-9_-]*) \"([^\"]*)\""); private final boolean acceptable; private final Matcher matcher;

public Request(String message) { matcher = PATTERN.matcher(message); acceptable = matcher.matches(); }

public String command() { return matcher.group(1); }

public List<String> parameters() { List<String> parameters = new ArrayList<>();

for (int i = 2; i <= matcher.groupCount(); i++) { parameters.add(matcher.group(i)); }

return parameters; }

public boolean isAcceptable() { return acceptable; } }

public class Send implements Job {

private final Request request;

public Send(Request request) { this.request = request; }

@Override public void run() throws IOException { if(request.isAcceptable()) new PushNotification(registrationId(), alert()).deliver(); }

private String registrationId() { return request.parameters().get(0); }

private String alert() { return request.parameters().get(1); } }

smell:Null Check

public class PushDaemon {

public void call(String message, Client client) { Request request = new Request(message); Job job = Job.create(client, request); if (job != null) { worker.accept(job); } } }

null communicates that an unknown command has been requested

Recipe:Null Object

public class NullJob implements Job { @Override public void run() throws IOException {

} }

public class PushDaemon {

public void call(String message, Client client) { Request request = new Request(message); Job job = Job.create(client, request); worker.accept(job); } }

public class PushDaemon {

public void call(String message, Client client) { Request request = new Request(message); Job job = Job.create(client, request); job.enqueueTo(worker); } }

public interface Job { void run() throws IOException;

public static Job create(Client client, Request request) {

Job job = null; if (request.isAcceptable()) { job = new NullJob(); } if ("PING".equals(request.command())) { job = new Ping(client); } else if ("SEND".equals(request.command())) { job = new Send(request); } return job; }

default void enqueueTo(Worker worker) { worker.accept(this); } }

public class NullJob implements Job { @Override public void run() throws IOException {

//noop }

@Override public void enqueueTo(Worker worker) {

//noop } }

Reference

http://tx.pignata.com/2013/04/mwrc-code-smells-talk.html

https://github.com/nicholasren/refactor_push

Q&A