Object Calisthenics Gabriel Heming
Gabriel Heming E-mail: gabriel.heming@hotmail.com
ORIGEM Cal • is • then • ics - /ˌkaləsˈTHeniks/ Origem grega; Exercícios no contexto de ginástica.
ORIGEM The ThoughtWorks Anthology - Jeff Bay; Conjunto de exercícios; Nove regras (não universais);
MOTIVAÇÃO Manutenibilidade; Legibilidade; Testabilidade; Compreensão.
RESUMINDO
Only one level of indentation per method; RULE #1: Only one level of indentation per method;
1 2 3 public String Board() { StringBuilder buf = new StringBuilder(); if (this.Data.Length >= 3) for (int i = 0; i < this.Data.Length; i++) for (int j = 0; j < this.Data[i].Length; j++) buf.Append(this.Data[i][j]); } buf.Append("\n"); return buf.ToString(); Validação 1 2 3 8
StringBuilder buf = new StringBuilder(); if (this.Data.Length < 3) public String Board() { StringBuilder buf = new StringBuilder(); if (this.Data.Length < 3) return buf.ToString(); } for (int i = 0; i < this.Data.Length; i++) for (int j = 0; j < this.Data[i].Length; j++) buf.Append(this.Data[i][j]); buf.Append("\n"); Early return 9
EXTRACT METHOD Martin Fowler
StringBuilder buf = new StringBuilder(); if (this.Data.Length < 3) public String Board() { StringBuilder buf = new StringBuilder(); if (this.Data.Length < 3) return buf.ToString(); } for (int i = 0; i < this.Data.Length; i++) buf.Append(this.CollectColumns(this.Data[i])); buf.Append("\n"); public String CollectColumns(string[] data) foreach (string row in data) buf.Append(row); 11
if (this.Data.Length < 3) return buf.ToString(); public String Board() { if (this.Data.Length < 3) return buf.ToString(); return this.CollectRows(this.Data); } public String CollectRows(string[][] data) StringBuilder buf = new StringBuilder(); foreach (string[] row in data) buf.Append(this.CollectColumns(row)); buf.Append("\n"); return buf.ToString(); public String CollectColumns(string[] data) foreach (string row in data) buf.Append(row); 12
Muito simples?
public static void SitefPaymentsAbort(Sales sales) { foreach (Payment payment in sales.Payments) if (payment.objSitefProvider != null && !string.IsNullOrEmpty(payment.objSitefProvider.receiptNumber)) payment.objSitefProvider.Abort(payment.objSitefProvider.receiptNumber, payment.objSitefProvider.DateTime); } foreach (TenderLineItem tenderlineitem in ((RetailTransaction)sales.transaction).TenderLines) if (tenderlineitem.LineId == payment.tenderLineItem.LineId) if (tenderlineitem is GiftCertificateTenderLineItem) if (sales.application.TransactionServices.CheckConnection()) bool released = true; string comment = ""; sales.application.TransactionServices.GiftCardRelease(ref released, ref comment, ((GiftCertificateTenderLineItem)tenderlineitem).SerialNumber); 14
public static void SitefPaymentsAbort(Sales sales) { foreach (Payment payment in sales.Payments) SalesOrder.SitefPaymentAbort(sales , payment); } private static void SitefPaymentAbort(Sales sales, Payment payment) if (payment.objSitefProvider != null && !string.IsNullOrEmpty(payment.objSitefProvider.receiptNumber)) payment.objSitefProvider.Abort(payment.objSitefProvider.receiptNumber, payment.objSitefProvider.DateTime); foreach (TenderLineItem tenderlineitem in ((RetailTransaction)sales.transaction).TenderLines) if (tenderlineitem.LineId == payment.tenderLineItem.LineId) if (tenderlineitem is GiftCertificateTenderLineItem) if (sales.application.TransactionServices.CheckConnection()) bool released = true; string comment = ""; sales.application.TransactionServices.GiftCardRelease(ref released, ref comment, ((GiftCertificateTenderLineItem)tenderlineitem).SerialNumber); 15
private static void SitefPaymentAbort(Sales sales, Payment payment) { /** código omitido **/ foreach (TenderLineItem tenderlineitem in ((RetailTransaction)sales.transaction).TenderLines) if (tenderlineitem.LineId == payment.tenderLineItem.LineId) if (tenderlineitem is GiftCertificateTenderLineItem) if (sales.application.TransactionServices.CheckConnection()) bool released = true; string comment = ""; sales.application.TransactionServices.GiftCardRelease(ref released, ref comment, ((GiftCertificateTenderLineItem)tenderlineitem).SerialNumber); } Validações 16
private static void SitefPaymentAbort(Sales sales, Payment payment) { /** código omitido **/ IEnumerable<TenderLineItem> tenderLineItemList = ((RetailTransaction)sales.transaction).TenderLines.Where( tenderLineItem => tenderLineItem.LineId == payment.tenderLineItem.LineId && tenderLineItem is GiftCertificateTenderLineItem ); foreach (TenderLineItem tenderlineitem in tenderLineItemList) if (sales.application.TransactionServices.CheckConnection()) bool released = true; string comment = ""; sales.application.TransactionServices.GiftCardRelease(ref released, ref comment, ((GiftCertificateTenderLineItem)tenderlineitem).SerialNumber); } 17
private static void SitefPaymentAbort(Sales sales, Payment payment) { /** código omitido **/ IEnumerable<TenderLineItem> tenderLineItemList = ((RetailTransaction)sales.transaction).TenderLines.Where( tenderLineItem => tenderLineItem.LineId == payment.tenderLineItem.LineId && tenderLineItem is GiftCertificateTenderLineItem ); foreach (TenderLineItem tenderlineitem in tenderLineItemList) SalesOrder.GiftCardRelease(sales, tenderLineItem); } private static void GiftCardRelease(SalesOrder sales, TenderLineItem tenderlineitem) if (sales.application.TransactionServices.CheckConnection()) bool released = true; string comment = ""; sales.application.TransactionServices.GiftCardRelease(ref released, ref comment, ((GiftCertificateTenderLineItem)tenderlineitem).SerialNumber); 18
public static void SitefPaymentsAbort(Sales sales) { foreach (Payment payment in sales.Payments) SalesOrder.SitefPaymentAbort(sales , payment); } private static void SitefPaymentAbort(Sales sales, Payment payment) if (payment.objSitefProvider != null && !string.IsNullOrEmpty(payment.objSitefProvider.receiptNumber)) payment.objSitefProvider.Abort(payment.objSitefProvider.receiptNumber, payment.objSitefProvider.DataHoraTransacao); IEnumerable<TenderLineItem> tenderLineItemList = ((RetailTransaction)sales.transaction).TenderLines.Where( tenderLineItem => tenderLineItem.LineId == payment.tenderLineItem.LineId && tenderLineItem is GiftCertificateTenderLineItem ); foreach (TenderLineItem tenderlineitem in tenderLineItemList) SalesOrder.GiftCardRelease(sales, tenderLineItem); private static void GiftCardRelease(SalesOrder sales, TenderLineItem tenderlineitem) if (sales.application.TransactionServices.CheckConnection()) bool released = true; string comment = ""; sales.application.TransactionServices.GiftCardRelease(ref released, ref comment, ((GiftCertificateTenderLineItem)tenderlineitem).SerialNumber); 19
BENEFÍCIOS S.R.P. (Single Responsibility Principle); Redução lógica por métodos; Legibilidade; Reusabilidade.
RULE #2: Don’t use the else keyword
Por que?
COMPLEXIDADE CICLOMÁTICA
Defensive Programming
public Entity Create(Entity entity) { if (entity.IsValid()) Repository repository = this.GetRepository(entity); if (!repository.Exists(entity)) return repository.Save(entity); } else throw new ArgumentException("The entity already exists"); throw new ArgumentException("Invalid entity"); 25
public Entity Create(Entity entity) { if (!entity.IsValid()) throw new ArgumentException("Invalid entity"); } Repository repository = this.GetRepository(entity); if (!repository.Exists(entity)) return repository.Save(entity); else throw new ArgumentException("The entity already exists"); 26
public Entity Create(Entity entity) { if (!entity.IsValid()) throw new ArgumentException("Invalid entity"); } Repository repository = this.GetRepository(entity); if (repository.Exists(entity)) throw new ArgumentException("The entity already exists"); return repository.Save(entity); 27
OUTRAS ABORDAGENS Chain of Responsibility Pattern; State Pattern.
BENEFÍCIOS Evita duplicidade; Aumenta a legibilidade; Reduz a complexidade ciclomática.
RULE #3: Wrap all primitives and STRINGS
Primitive Obsession anti-pattern
SOMENTE TIPOS COM COMPORTAMENTO
87,43 Sub 0?? 77,43 ????????? Add -10?? class BankAccount { private double amount; public void Add(double money) this.amount += money; } public void Sub(double money) this.amount -= money; public double Total() return this.amount; BankAccount account = new BankAccount(); account->Add(10); account->Add(12.25); account->Add(27.50); account->Add(55); account->Sub(17.32); account->Total(); 87,43 account->Add(-10); account->Sub(0); account->Total(); Sub 0?? 77,43 ????????? Add -10?? 33
Lógica duplicada class BankAccount { private double amount; public void Add(double money) if (money <= 0) throw new ArgumentException("money não pode ser igual ou menor que zero"); } this.amount += money; public void Sub(double money) this.amount -= money; public double Total() return this.amount; Lógica duplicada 34
private double amount; public Money(double amount) if (money <= 0) class Money { private double amount; public Money(double amount) if (money <= 0) throw new ArgumentException("money não pode ser igual ou menor que zero"); } this.amount = amount; public double GetAmount() return this.amount; class BankAccount { private double amount; public void Add(Money money) this.amount += money->GetAmount(); } public void Sub(Money money) this.amount -= money->GetAmount(); public double Total() return this.amount; BankAccount account = new BanckAccount(); account->Add(new Money(10)); account->Add(new Money(-10)); // throw ArgumentException 35
BENEFÍCIOS Evita duplicidade; Encapsulamento; Domain Driven Design.
First class collections RULE #4: First class collections
public List<Address> AddressList { get; internal set; } } public class Person { public List<Address> AddressList { get; internal set; } } public class Address public Type Type { get ; set; } public enum Type Residential, Commercial List<Address> residentialAddressList = person.AddressList.Where( address => address.Type == Type.Residential ).ToList() List<Address> residentialAddressList = person.AddressList.Where( address => address.Type == Type.Commercial ).ToList() 38
E SE PRECISAR FILTRAR EM DIFERENTES LUGARES?
Responsabilidades demais class Person { public List<Address> AddressList { get; internal set; } public List<Address> GetResidentialAddressList() return this.AddressList.Where( address => address.Type == Type.Residential).ToList(); } public List<Address> GetCommercialAddressList() address => address.Type == Type.Commercial).ToList(); Responsabilidades demais List<Address> residentialAddressList = person.GetResidentialAddressList(); List<Address> commercialAddressList = person.GetCommercialAddressList(); 40
CADA UM COM SUA RESPONSABILIDADE
public class AddressCollection { public List<Address> GetResidentialAddressList() return this.AddressList.Where( address => address.Type == Type.Residential).ToList(); } public List<Address> GetCommercialAddressList() address => address.Type == Type.Commercial).ToList(); public class Person public AddressCollection AddressCollection { get; internal set; } List<Address> residentialAddressList = person.AddressCollection.GetResidentialAddressList(); List<Address> commercialAddressList = person.AddressCollection.GetCommercialAddressList(); 42
BENEFÍCIOS S.R.P. (Single Responsibility Principle); Evita duplicação; Manutenibilidade.
RULE #5: One dot per line
TRAIN WRECKS Robert UncleBob Martin
this.Repository.Database.Config.SetAttribute("attribute" , "value"); E se houver um null? 46
Null object?
POR QUE NÃO? Esconde um problema de encapsulamento; Difícil de debugar e tratar exceções; O código deve ser reestruturado e tomar consciência da existência dele; Prejudica a leitura e o entendimento.
LAW OF DEMETER
this.Repository.SetDatabaseConfig("attribute", "value"); Nem sempre é aplicável 50
EXCEÇÕES Fluent Interface; Builders (ex.: Query Builder, Link Builder, etc...);
BENEFÍCIOS Lei de Demeter; O.C.P (Open/Closed Principle); Legibilidade; Testabilidade; Debug.
RULE #6: Don’t abbreviate
POR QUE ABREVIAR? Escrita repetitiva? Nomes muito longos? “There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.”
BENEFÍCIOS S.R.P. (Single Responsibility Principle); Comunicação; Legibilidade; Manutenibilidade.
Keep all entities small RULE #7: Keep all entities small
DIRETRIZES Classes com 50 linhas; Leitura com pouco uso de scroll; Apenas desenvolva o que for utilizar.
BENEFÍCIOS S.R.P. (Single Responsibility Principle); Legibilidade; Encapsulamento; Segregação; Coesão e Coerência.
No classes with more than two instance variables RULE #8: No classes with more than two instance variables
ESSA É DIFÍCIL
List<String> names; class Name { String first; String middle; String last; } class Name { Surname family; GivenNames given; } class Surname String family; class GivenNames List<String> names; 61
Por que somente duas?
COMPLEMENTAR A REGRA #2
BENEFÍCIOS S.R.P. (Single Responsability Principle); Baixo acoplamento; Encapsulamento; Coesão e Coerência; Testabilidade.
No getters / setters / properties RULE #9: No getters / setters / properties
POR QUE? Simplesmente uma questão comportamental.
public int X { get; set; } public int Y { get; set; } } class Rectangle { public int X { get; set; } public int Y { get; set; } } Rectangle rectangle = new Rectangle() { X = 10, Y = 5 }; int area = rectangle.X * rectangle.Y; 67
TELL, DON’T ASK!
public Rectangle(int x , int y) this.X = x; this.Y = y; } class Rectangle { private int X; private int Y; public Rectangle(int x , int y) this.X = x; this.Y = y; } public int TotalArea() return this.X * this.Y; Rectangle rectangle = new Rectangle(10, 5); int area = rectangle.TotalArea(); 69
BENEFÍCIOS O.C.P (Open/Closed Principle); Encapsulamento.
CONSIDERAÇÕES
QUESTIONS? Thanks!