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
The Legacy Code
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

Embracing the-power-of-refactor

  • 1.
    Embracing the Powerof Refactor REN Xiaojun
  • 2.
    Code smells areheuristics for refactoring
  • 3.
    Our design communicatesto us through resistance.
  • 4.
  • 5.
  • 6.
  • 7.
    This resistance isvaluable feedback
  • 8.
    Code smells arehints from our software about how to reduce this resistance.
  • 9.
    This is oneway our design emerges.
  • 11.
  • 12.
  • 13.
    Queue<String> queue =new LinkedBlockingQueue<>(); HttpClient httpclient = HttpClients.createDefault(); DatagramSocket socket = new DatagramSocket(6889);
  • 14.
    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
  • 15.
    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
  • 16.
  • 17.
  • 18.
  • 19.
    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); } }
  • 20.
    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(); } } } }
  • 21.
  • 22.
    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
  • 23.
    public void start(){ spawnWorkers(); while(true){ processRequests(); } }
  • 24.
    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(); } }
  • 25.
    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(); } }
  • 26.
    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() { //… } }
  • 27.
    Update authorization key Increasethread 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
  • 28.
  • 29.
  • 30.
    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(); } }
  • 31.
    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); } }
  • 32.
    public class PushDaemon{ public PushDaemon() throws SocketException { queue = new LinkedBlockingQueue<>(); httpclient = HttpClients.createDefault(); socket = new DatagramSocket(6889); } }
  • 33.
    public class PushDaemon{ public PushDaemon() throws SocketException { worker = new Worker(); socket = new DatagramSocket(6889); } }
  • 34.
    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(); } }
  • 35.
    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(); } }
  • 36.
    public class PushDaemon{ public void start() { spawnWorkers(); while(true){processRequests();} } }
  • 37.
    public class PushDaemon{ public void start() { worker.spawn(); while(true){processRequests();} } }
  • 38.
    public class PushDaemon{ public PushDaemon() throws SocketException { worker = new Worker(); socket = new DatagramSocket(6889); } }
  • 39.
    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(); } } }
  • 40.
    public class UDPServer{ private final DatagramSocket socket; public UDPServer() throws SocketException { socket = new DatagramSocket(6889); } }
  • 41.
    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
  • 42.
    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); } }
  • 43.
    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(); } }
  • 44.
    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() { //… } }
  • 45.
    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(); } }
  • 46.
  • 49.
    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() { //… } }
  • 50.
    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() { //… } }
  • 51.
    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() { //… } }
  • 52.
    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() { //… } }
  • 53.
    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(); } }
  • 54.
    private void processRequests(DatagramPacketreceived) { 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(); } }
  • 55.
    private void call(DatagramPacketreceived) { 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(); } }
  • 56.
    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); } }
  • 57.
    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(); } } }
  • 58.
  • 59.
  • 60.
    public void call(DatagramPacketreceived) { 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(); } } }
  • 61.
    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()); } }
  • 62.
    public void call(DatagramPacketreceived) { 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(); } } }
  • 63.
    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; } }
  • 64.
    public void call(DatagramPacketreceived) { 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(); } } }
  • 65.
    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(); } }
  • 66.
    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); } } }
  • 67.
    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(); } }
  • 68.
    public interface Job{ void run() throws IOException; } public class Ping implements Job { //… } public class Send implements Job { //… }
  • 69.
    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); } } }
  • 70.
  • 71.
    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; } }
  • 72.
    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); } } }
  • 73.
    public class Pingimplements 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); } } }
  • 74.
    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(); } }
  • 75.
    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()); } } }
  • 76.
    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()); } }
  • 77.
    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; } }
  • 78.
    public interface Job{ public static Job create(Client client, String message, UDPServer server) { //… } }
  • 79.
    public class Pingimplements 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()); } }
  • 80.
  • 81.
    Extracted objects tendto attract behaviour.
  • 82.
    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()); } }
  • 83.
    public class Pingimplements 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()); } }
  • 84.
    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()); } }
  • 85.
    public class Pingimplements Job { public void run() throws IOException { client.send("PONG"); } }
  • 86.
    public class Sendimplements 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); } } }
  • 87.
  • 88.
    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 + ""}}"; } }
  • 89.
    public class Sendimplements 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(); } } }
  • 90.
  • 91.
    we’re using simpledata types to represent complex ideas
  • 92.
    public class Sendimplements 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(); } } }
  • 93.
  • 94.
  • 95.
    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; } }
  • 96.
    public class Sendimplements 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); } }
  • 97.
  • 98.
    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); } } }
  • 99.
    null communicates thatan unknown command has been requested
  • 100.
  • 101.
    public class NullJobimplements Job { @Override public void run() throws IOException { } }
  • 102.
    public class PushDaemon{ public void call(String message, Client client) { Request request = new Request(message); Job job = Job.create(client, request); worker.accept(job); } }
  • 103.
    public class PushDaemon{ public void call(String message, Client client) { Request request = new Request(message); Job job = Job.create(client, request); job.enqueueTo(worker); } }
  • 104.
    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); } }
  • 105.
    public class NullJobimplements Job { @Override public void run() throws IOException { //noop } @Override public void enqueueTo(Worker worker) { //noop } }
  • 106.
  • 107.