Um conselho...
Codifique pensando que outras pessoas devem entender seu código,
por mais que ninguém vá ver,
pois isso aumenta consideravelmente as chances de no futuro
você mesmo entender esse código.
Para tudo existe uma lógica. A minha é essa!
import java.util.Date; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; public class Case1 { public void abstractController() throws Exception { try { // dados recebidos de uma fonte qualquer String de = "eu@email.com"; String para = "qualquer@email.com"; String assunto = "Refactoring na Prática"; String corpo = "Corpo da Mensagem"; // validacao de email Pattern p = Pattern.compile(".+@.+\\.[a-z]+"); Matcher m1 = p.matcher(de); if (!m1.matches()) throw new IllegalArgumentException("Email address is invalid: \"" + de + "\""); Matcher m2 = p.matcher(para); if (!m2.matches()) throw new IllegalArgumentException("Email address is invalid: \"" + para + "\""); Properties props = new Properties(); props.put("mail.host", "127.0.0.1"); Session session = Session.getInstance(props); MimeMessage msg = new MimeMessage(session); try { msg.setFrom(new InternetAddress(de)); InternetAddress[] to = {new InternetAddress(para)}; msg.setRecipients(Message.RecipientType.TO, to); msg.setSentDate(new Date()); msg.setSubject(assunto); msg.setText(corpo); Transport.send(msg); } catch (MessagingException e) { // omitido } } catch (Exception e) { // omitido } } }A classe Case1 é o primeiro caso que quero mostrar. Em seu método abstractController ela executa a lógica de receber os atributos que identificam um e-mail, validar alguns deles, montar o e-mail utilizando a biblioteca javax.mail, então por fim, enviar o e-mail. Um método com muitas responsabilidades.
import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Properties; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; public class Case2 { /** * Refactoring: * - Criacao da classe EmailAddress */ public void abstractController() { EmailAddress de = new EmailAddress("eu@email.com"); EmailAddress para = new EmailAddress("qualquer@email.com"); String assunto = "Refactoring na Prática"; String corpo = "Corpo da Mensagem"; Properties props = new Properties(); props.put("mail.host", "127.0.0.1"); Session session = Session.getInstance(props); MimeMessage msg = new MimeMessage(session); try { msg.setFrom(de.toInternetAddress()); InternetAddress[] to = {para.toInternetAddress()}; msg.setRecipients(Message.RecipientType.TO, to); msg.setSentDate(new Date()); msg.setSubject(assunto); msg.setText(corpo); Transport.send(msg); } catch (MessagingException e) { throw new RuntimeException(e); } } }Nesse ponto do código, uma classe extra foi criada com a intenção de centralizar o e-mail bem como sua validação. A classe EmailAddress tem a responsabilidade de fazer apenas isso, representar um e-mail. Vale lembrar que em um refactoring não se pode alterar o fluxo que a aplicação já possui, pois isso seria tarefa em uma correção ou manutenção. A classe EmailAddress segue o padrão Tiny Type, que são pequenos tipos de objeto que encapsulam pouca lógica.
import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; /** * Tiny Type Pattern * * @author Vinicius Martins Knob */ public final class EmailAddress { private final InternetAddress _emailAddress; /** * Cria um EmailAddress a partir de um email * @param emailAddress * @throws AddressException */ public EmailAddress(String emailAddress) throws AddressException { validar(emailAddress); _emailAddress = new InternetAddress(emailAddress); } private void validar(String emailAddress) { Pattern p = Pattern.compile(".+@.+\\.[a-z]+"); Matcher m = p.matcher(emailAddress); if (!m.matches()) throw new IllegalArgumentException("Email address is invalid: \"" + emailAddress + "\""); } @Override public String toString() { return _emailAddress.getAddress(); } public InternetAddress toInternetAddress(){ return _emailAddress; } /** * Retorna uma lista imutavel de EmailAddress a partir de um array de emails. * @param emails * @return * @throws AddressException */ public static List<EmailAddress> asList(String... emails) throws AddressException { if (emails.length == 0) return Collections.emptyList(); List<EmailAddress> foo = new ArrayList<EmailAddress>(); for (String bar : emails) { foo.add(new EmailAddress(bar.trim())); } return Collections.unmodifiableList(foo); } }O que ganho com isso?
import java.util.Date; import java.util.Properties; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; public class Case3 { /** * Refactoring: * - Criacao da classe Email */ public void abstractController() { EmailAddress de = new EmailAddress("eu@email.com"); EmailAddress para = new EmailAddress("qualquer@email.com"); Email email = Email.create() .from(de) .to(para) .subject("Refactoring na Prática") .body("Corpo da Mensagem") .build(); Properties props = new Properties(); props.put("mail.host", "127.0.0.1"); Session session = Session.getInstance(props); MimeMessage msg = new MimeMessage(session); try { msg.setFrom(de.toInternetAddress()); InternetAddress[] to = {para.toInternetAddress()}; msg.setRecipients(Message.RecipientType.TO, to); msg.setSentDate(new Date()); msg.setSubject(email.getSubject()); msg.setText(email.getBody()); Transport.send(msg); } catch (MessagingException e) { throw new RuntimeException(e); } } }No caso 3 externalizei uma segunda parte da lógica, a montagem do e-mail. A montagem consiste na coleta dos dados e suas respectivas validações. Para isso criei a classe Email. Ela segue o padrão Builder.
import java.text.MessageFormat; import java.util.List; /** * * Esta classe segue o Builder Pattern * * @author Vinicius Martins Knob */ public class Email { private EmailAddress _from; private List<EmailAddress> _to; private String _subject; private String _body; private Email() {} public EmailAddress getFrom() { return _from; } public List<EmailAddress> getTo() { return _to; } public String getSubject() { return _subject; } public String getBody() { return _body; } @Override public String toString() { String pattern = "[from: {0}, to: {1}, subject: {2}, body: {3}]"; Object[] data = {_from, _to.toString(), _subject, _body}; return MessageFormat.format(pattern, data); } @Override public boolean equals(Object obj) { return this.toString().equals(obj.toString()); } @Override public int hashCode() { int result = 17; result = 31 * result * (_from==null?0:_from.hashCode()); result = 31 * result * (_to==null?0:_to.hashCode()); result = 31 * result * (_subject==null?0:_subject.hashCode()); result = 31 * result * (_body==null?0:_body.hashCode()); return result; } /** * * Builder Method - Builder Pattern * */ public static EmailBuilder create() { return new EmailBuilder(); } /** * * Builder Class - Builder Pattern * <br>Inner Class * */ public static class EmailBuilder{ private final Email _email = new Email(); private EmailBuilder(){} public EmailBuilder from(EmailAddress from){ _email._from = from; return this; } public EmailBuilder to(List<EmailAddress> to){ _email._to = to; return this; } public EmailBuilder subject(String subject){ _email._subject = subject; return this; } public EmailBuilder body(String body){ _email._body = body; return this; } public Email build(){ return _email; } } }A classe Email contempla o padrão Builder, padrão aplicado em virtude da quantidade de parâmetros utilizados, e a tendência dessa classe, dependendo do sistema, é aumentar a quantidade desses parâmetros. Cada método poderia conter a validação respectiva ao dado inserido, centralizando toda a lógica apenas em uma classe, para todo sistema. A classe Email faz a coleta e validação de dados de um e-mail, e só isso. Os atributos atendidos pela classe EmailAddress são também atendidos aqui: coesão, responsabilidade única, legibilidade e baixo acoplamento.
import java.util.Arrays; public class Case4 { /** * Refactoring: * - Criacao da classe Sender */ public void abstractController() { try { EmailAddress de = new EmailAddress("eu@email.com"); EmailAddress para = new EmailAddress("qualquer@email.com"); Email email = Email.create() .from(de) .to(Arrays.asList(para)) .subject("Refactoring na Prática") .body("Corpo da mensagem") .build(); Sender.send(email); } catch (Exception e) { // log } } }No caso 4, uma terceira lógica foi movida do controller para uma classe específica, a classe Sender. Essa classe passa a ser responsável pelo envio do e-mail, e somente isso. Ela encapsula boa parte da API de envio de e-mail do java (javax.mail) e possui apenas um método estático, send(email). O parâmetro de send é um objeto do tipo Email, a classe criada no caso 3. O contrutor de Sender lança uma UnsupportedOperationException, pois não existe motivos para instanciar essa classe, ela foi projetada para apenas enviar um e-mail, não armazenando dado algum. O seu método private getArrayAddress efetua uma conversão da lista de EmailAddress para um array de InternetAddress, exigência esta imposta pela API javax.mail.
import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Properties; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.naming.NamingException; /** * <p> * Classe responsavel por enviar um email. * </p> * <p> * Classe nao-instanciavel, seu unico proposito eh enviar um email, * funcionalidade essa fornecidade pelo metodo {@link #send(Email)}. * </p> * <p> * <b>OBS:</b> Classe nao projetada para heranca. * </p> * * @author Vinicius Martins Knob * */ public final class Sender { private Sender() { throw new UnsupportedOperationException("classe nao-instanciavel"); } public static void send(Email email) throws NamingException { Properties p = new Properties(); p.put("mail.host", "127.0.0.1"); Session session = Session.getInstance(p); MimeMessage msg = new MimeMessage(session); try { msg.setFrom(email.getFrom().toInternetAddress()); msg.setRecipients(Message.RecipientType.TO, getArrayAddress(email.getTo())); msg.setSentDate(new Date()); msg.setSubject(email.getSubject()); msg.setText(email.getBody()); Transport.send(msg); } catch (MessagingException e) { throw new RuntimeException(e); } } private static InternetAddress[] getArrayAddress(List<EmailAddress> emails){ List<InternetAddress> internetAddresses = new ArrayList<InternetAddress>(); for (EmailAddress address : emails) { internetAddresses.add(address.toInternetAddress()); } return internetAddresses.toArray(new InternetAddress[internetAddresses.size()]); } }
"A perfeição é feita de pequenos detalhes - não é apenas um detalhe." (Michelangelo)
public boolean isEmpty() { return count == 0; }
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String) anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n-- != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; }
public class StringEmptyTest { @Test public void isEmpty() { String test = ""; for (int i = 0; i < 100000000; i++) { test.isEmpty(); } } @Test public void equals() { String test = ""; for (int i = 0; i < 100000000; i++) { test.equals(""); } } }
public final class HashCode { private HashCode() {} public static HashCode create() { return new HashCode(); } private int _result = 17; public HashCode append(int value) { _result = 31 * _result + value; return this; } public HashCode append(boolean value) { return append(value?1:0); } public HashCode append(byte value) { return append((int)value); } public HashCode append(char value) { return append((int)value); } public HashCode append(short value) { return append((int)value); } public HashCode append(long value) { return append((int)(value^(value>>>32))); } public HashCode append(float value) { return append(Float.floatToIntBits(value)); } public HashCode append(double value) { return append(Double.doubleToLongBits(value)); } public HashCode append(Object value) { return append(value==null?0:value.hashCode()); } @Override public int hashCode() { return _result; } }
@Override public int hashCode() { return HashCode.create() .append(_name) // String .append(_url) // URL .append(_descriptiveStatus) // String .append(_timer) // long .append(_effect) // boolean .hashCode(); }
@Override public int hashCode() { HashCode result = HashCode.create(); result.append(_nameBedroom); // String Enumeration<String> keys = _bedroom.keys(); while (keys.hasMoreElements()) { String key = (String) keys.nextElement(); result.append(_bedroom.get(key).hashCode()); // int } return result.hashCode(); }
public class HashCodeTest { String nome = "Vinicius Knob"; int anoNascimento = 1989; long dataHoje = Calendar.getInstance().getTimeInMillis(); boolean java = true; @Test public void testSemClasse() { for (int i = 0; i <= 100000000; i++) { int result = 17; result = 31 * result + nome.hashCode(); result = 31 * result + anoNascimento; result = 31 * result + (int)(dataHoje^(dataHoje>>>32)); result = 31 * result + (java?1:0); } } @Test public void testComClasse() { for (int i = 0; i <= 100000000; i++) { HashCode.create().append(nome) .append(anoNascimento).append(dataHoje) .append(java).hashCode(); } } }
public class HashCodeTest { String nome = "Vinicius Knob"; int anoNascimento = 1989; long dataHoje = Calendar.getInstance().getTimeInMillis(); boolean java = true; @Test public void testSemClasse() { int cacheResult = 0; for (int i = 0; i <= 100000000; i++) { if (cacheResult == 0) { int result = 17; result = 31 * result + nome.hashCode(); result = 31 * result + anoNascimento; result = 31 * result + (int)(dataHoje^(dataHoje>>>32)); result = 31 * result + (java?1:0); cacheResult = result; } } } @Test public void testComClasse() { int cacheResult = 0; for (int i = 0; i <= 100000000; i++) { if (cacheResult == 0) { cacheResult = HashCode.create().append(nome) .append(anoNascimento).append(dataHoje) .append(java).hashCode(); } } } }
Full Stack Developer e Codificador Limpo. Swing/Position Trader. Praticante de Corrida e Hapkido.