Pq os teste unitários não são tão populares!

93 respostas
L
Vira e mexe vem alguém e fala que os teste unitários não são legais, não funcionam não dão certo por esse ou aquele motivo. Eu uso teste unitários, a mais ou menos um ano, não consigo mais me imaginar passando horas debugando código  pq implementei  uma nova funcionalidade:
 Li o artigo no javaworld e o cara faz um pergunta interessante colocação, que eu gostaria de fazer a vc's.  " Eu adoraria ouvir estórias de situações mundo real aonde os teste unitários foram abandonados, ou simplesmente rejeitados e pq?"

93 Respostas

P

Te digo um lugar onde os testes foram rejeitados: num antigo projeto meu.

Mas eu era testador e os testes unitários, legados. Num port do sistema para uma nova arquitetura os programadores, que eram muito bons, decidiram que seria mais rapido desenvolver sem testes.

É claro que envolvia coisas simples como umas JNIs, umas Brigdes e bastante codigo novo para suportar uma funcionalidade boba, um tal de IPv6.

O projeto, depois de 6 meses, foi posto na geladeira…

S

Andei vendo vários programadores preferindo fazer teste de integração ao invés de teste unitário. Isso normalmente acontece quando o cara é preguiçoso ou acredita que integração cobre tudo que ele precisa. Daí quando alguma coisa começa dar errado tem que sair debugando código. É lindo de se ver :lol:

D

Testes Unitários não garantem que seu sistema está funcionando.
Testes Integrados não garantem que o seu programa está 100%.

Não se pode ficar sem fazer testes unitarios e não se pode ficar sem fazer testes integrados, ter os dois seria o cenario ideal.

Oq acontece é que alguns projetistas de testes planejam testes unitários inuteis, por isso numa empresa que trabalhei os testers não eram muito populares!

Só pra ter idéia, o sistema web tinha testes do tipo:

-Aperte Esc duas vezes para ver se sai do navegador.

Isso sim é um teste inutil!

S

Sem entrar no mérito se testes unitários é tão importante para os programadores Java quanto a ressurreição para os cristãos (procure no GUJ que há vários tópicos com esse debate), gostaria de expressar minha opinião de desacordo com a afirmação abaixo:

Eu entendo que programação OO requer organização e principalmente separação de responsabilidades. Num código com uma arquitetura e organização minimamente descentes, estender o sistema não pode fundamentalmente ter como consequencia a sua quebra. Muitos, tb por causa dos testes unitários, abominam herança. É estranho entender então porque todas as linguagens OO que surgiram nos últimos 10 anos possuem herança. Será que é porque herança, igualmente junto a interfaces do Java, ajuda a estender um sistema sem quebrar o que já funciona hoje?

Por favor: esqueçam a história do MyProperties extends Properties. Nesse caso é claro que é melhor usar composição, a não ser que vc queira passar o seu novo objeto para um sistema legado que utiliza properties. O certo aqui, como todo mundo já deve saber, é preferir interfaces como java.util.Map, mas infelizmente o properties não implementa nenhuma interface.

Eu acredito que se para qualquer estenção ou mudança no seu código vc precisa de testes unitários, então o seu código é um spagetti code. Acho que as pessoas confundem muito sistema com algoritimo. O primeiro pode existir tranquilamente sem testes unitários, já o segundo pode tirar bastante proveito de testes unitários, principalmente se o algoritmo vai evoluir e ser modificado diversas vezes.

E então temos TDD, que eu entendo pouco, mas que acho ser uma metodologia de desenvolvimento que usa testes unitários como especificação. Se for isso não tem nada haver com esse debate…

R

Eu gosto muito de testes unitários, ajudam a pegar muitos erros antes de subir a aplicação inteira e ajuda a ver erros de design.

Mas acho que os testes unitários não são suficentes para garantir que o aplicativo funciona. O erro muitas vezes esta bem na parte onde os objetos se “integram” ou se “comunicam”. Além disso, o fato de seu unit test ter passado não significa que a aplicação esta fazendo o que ela deveria fazer.

L

Olá

Sou amplamente favorável aos testes unitários. Acho que quem tem este hábito programa de um modo mais seguro. Mas faço 3 observações:

  1. É óbvio que não são suficientes para garantir que o aplicativo funciona

  2. Acho bobagem buscar cobertura 100% principalmente em uma linguagem verbosa como o Java

  3. Como disse ontem, infelizmente só conheço um único livro que ensina a programar fazendo testes unitários

[]s
Luca

P

100% de cobertura de teste nunca é 100% de cobertura de testes.

mas 90% de cobertura de testes é muito melhor do que ?% – se trata de responsabilidade.

D

Trabalhei numa consultoria que presta serviço pra um banco, lá haviam algumas coisas bem toskas com relação a testes.

Um os testes unitários eram feitos após o build e não durante.

Erros nos testes unitários/integrados eram reportados e cobrados financeiramente da consultoria.

Não falo as empresas por ética.

Q coisa não?

E

Poderia dizer qual é esse livro?

Obrigado.

E

Pensando aqui, seria ótimo q os milhares de tutoriais de centenas de tecnologias, frameworks, hello worlds, etc.

Bem como aqueles pedaçoes de código de exemplo, se tivessem testes unitários poderiam ajudar a disseminar mais os testes …

R

Unit test para Hello World?

L

Olá

eric_jf:
Luca:

3) Como disse ontem, infelizmente só conheço um único livro que ensina a programar fazendo testes unitários

Poderia dizer qual é esse livro?

Se não me engano é este: Agile Java Crafting Code with Test-Driven Development

Quando aprendi Fortran em 1968 meu primeiro programinha NÃO foi um HelloWorld. Foi multiplicação de matrizes.

Mas já que atualmente sempre se usa o HelloWorld, porque não fazer teste unitário? Eu acho que seria muito bom já incluir teste unitário no primeiro HelloWorld. Se fosse assim a gente não precisava ler o que eu li ontem dizendo que escrever testes unitários consome tempo exagerado.

[]s
Luca

R
public class HelloWorld {
   public static void main(String[] args) {
       System.out.println("Hello World!");
   }
}

//...
public class HelloWorldTest extends TestCase {

    public void testMain() {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        System.setOut(new PrintStream(bos));
        HelloWorld.main(null);
        assertEquals("Hello World!\r\n", bos.toString());
   }
}
S

Luca:
Olá

eric_jf:

Poderia dizer qual é esse livro?

Se não me engano é este: Agile Java Crafting Code with Test-Driven Development


E quanto ao JUnit in Action? Você o recomendou em outro tópico.
Ou ele não ensina a programar (só ensina testes)?

C

Oi,

Os testes (tanto unitários quanto de integração) devem fazer parte de um desenvolvimento de software. Desenvolvedores profissionais desenvolvem com testes.

Se os testes não estão tão populares, cabe à nós desenvolvedores profissionais a fazer uma maior divulgação da importância dos testes.

L

Olá

Schuenemann:
E quanto ao JUnit in Action? Você o recomendou em outro tópico.
Ou ele não ensina a programar (só ensina testes)?

O Junit in action é muito bom. Eu li e acho que vale a pena mas é para quem já sabe Java.

Já o livro anterior eu só dei uma olhada e achei curioso justamente por ensinar a programar já com testes.

[]s
Luca

H

Existe MUITOSSS livros sobre teste unitarios… JUnit in Action, JUnit Recipes, Test Driven in Action, isso da editora Manning…

Mas querendo ou não, teste unitario demanda tempo, paciencia, disciplina e habilidade. E em ambientes onde nao te proporcionam isso é muito dificil colocar para funcionar essa prática.

Outra coisa, querendo ou não, testar é dificil… Não digo dificil para seres perfeitos como muitos, ou como eu 8), mas para testar requer bom entendimento de OO, requer conhecimentos de refatoração e, principalmente, necessita entender o valor dos testes unitarios… Senão vira mais uma coisa chata para fazer…

L

saoj:
Sem entrar no mérito se testes unitários é tão importante para os programadores Java quanto a ressurreição para os cristãos (procure no GUJ que há vários tópicos com esse debate), gostaria de expressar minha opinião de desacordo com a afirmação abaixo:

Eu entendo que programação OO requer organização e principalmente separação de responsabilidades. Num código com uma arquitetura e organização minimamente descentes, estender o sistema não pode fundamentalmente ter como consequencia a sua quebra. Muitos, tb por causa dos testes unitários, abominam herança. É estranho entender então porque todas as linguagens OO que surgiram nos últimos 10 anos possuem herança. Será que é porque herança, igualmente junto a interfaces do Java, ajuda a estender um sistema sem quebrar o que já funciona hoje?

Por favor: esqueçam a história do MyProperties extends Properties. Nesse caso é claro que é melhor usar composição, a não ser que vc queira passar o seu novo objeto para um sistema legado que utiliza properties. O certo aqui, como todo mundo já deve saber, é preferir interfaces como java.util.Map, mas infelizmente o properties não implementa nenhuma interface.

Eu acredito que se para qualquer estenção ou mudança no seu código vc precisa de testes unitários, então o seu código é um spagetti code. Acho que as pessoas confundem muito sistema com algoritimo. O primeiro pode existir tranquilamente sem testes unitários, já o segundo pode tirar bastante proveito de testes unitários, principalmente se o algoritmo vai evoluir e ser modificado diversas vezes.

E então temos TDD, que eu entendo pouco, mas que acho ser uma metodologia de desenvolvimento que usa testes unitários como especificação. Se for isso não tem nada haver com esse debate…

Concordo contigo, os testes não estão em uma lado oposto à herança, aliás isso é novidade para mim, não faço nem idéia de como alguém chega a esse tipo de conclusão.

L

Isso é um dos motivo que fazem os testes unitários não serem tão populares assim, pelo menos é o que diz o autor do artigo no javaworld.

L

Olá

Como disse hoje ou ontem em outro tópico, o Peter Coad foi o primeiro autor que li recomendando favorecer composição sobre herança e isto foi em 1999. Anos depois o Joshua Bloch e o Rod Johnson enfatizaram muito isto.

Mas foi quando surgiu a onda de testes unitários é que começou a cair a ficha de muita gente porque com herança ficava muito mais difícil testar. É por isto que alguém deve ter escrito que testes unitários não combinam com herança.

Hoje todo mundo sabe dos defeitos da herança mas se este estágio foi alcançado, além da popularização das ideías do Coad, Bloch e Johnson, com certeza a onda de testes unitários tem sua parcela de contribuição.

[]s
Luca

S

Sim, hoje todo mundo sabe os defeitos de herança e quando favorecer composição sobre herança. O que pouquíssima gente parece entender é quando e como herança deve ser usada, isto é, quando ela justifica grandeosamente a sua razão de existir. Ou será ela algo totamente inútil que pode ser removido tranquilamente de qualquer linguagem OO?

As pessoas idolatram tanto os testes unitários que fecharam os olhos para a herança e como ela pode “estender sem quebrar”, ou seja, permitir a evolução do sistema e a criação de novas funcionalidades sem necessariamente quebrar o que já existe. Mas ela prejudica testes unitários, o que vc está falando? Está maluco?. Bom, “a única diferença de um maluco e eu é que eu sei que não sou maluco.”

Não é que as pessoas não entendem os defeitos de herança por causa de testes unitários. Mas sim que testes unitários colaboram para que as pessoas reneguem e não entendam herança. Se vc está começando agora a programar OO, testes unitários poderão lhe dar o ilusório conforto de que vc está fazendo a coisa direito. A mensagem parece ser essa: “Programe como quiser, faça uma arquitetura bem interdependente e enroscada, onde tudo depende de tudo. E se der verdinho parabéns!”

Testes unitários é a rodinha da bicicleta. O problema é que pouca gente tem afirmado que é preciso aprender e gostar de andar sem rodinha. E existem outras maneiras, automatizadas ou não, unitárias ou não, de testar se pelo menos o que vc fez está funcionando… Se fez direito aí o computador não tem como dizer e é nessa hora que a programação deixa de ser uma chata ciência exata e vira arte… O meu Hello World é mais bonito que o seu…

E
Luca:
Olá

Quando aprendi Fortran em 1968 meu primeiro programinha NÃO foi um HelloWorld. Foi multiplicação de matrizes.

Mas já que atualmente sempre se usa o HelloWorld, porque não fazer teste unitário? Eu acho que seria muito bom já incluir teste unitário no primeiro HelloWorld. Se fosse assim a gente não precisava ler o que eu li ontem dizendo que escrever testes unitários consome tempo exagerado.

[]s
Luca

Realmente para hello world por um lado pode ser exagero por outro criar a cultura de usar testes unitários é válida. E não apenas para HW, mas para exemplos de códigos, vários frameworks tem dezenas de exemplos de como fazer, mas nenhum deles com teste. Quem está começando se olha o tetse pode se interessar, expandir e até mesmo entender melhor o proprio framework. E não estou comentando de testes dos frameworks e sim dos exemplos em cima deles.

Rubem Azenha:
public class HelloWorld {
   public static void main(String[] args) {
       System.out.println("Hello World!");
   }
}

//...
public class HelloWorldTest extends TestCase {

    public void testMain() {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        System.setOut(new PrintStream(bos));
        HelloWorld.main(null);
        assertEquals("Hello World!\r\n", bos.toString());
   }
}

Pois é é bem isso que to falando, se houvessem mais códigos assim, eu acho q facilitaria a adoção. Na primeira mexida no código qdo o teste falha quem está aprendendo logo percebe uma das vantagens e o poder disso.

B
Rubem Azenha:
public class HelloWorld {
   public static void main(String[] args) {
       System.out.println("Hello World!");
   }
}

//...
public class HelloWorldTest extends TestCase {

    public void testMain() {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        System.setOut(new PrintStream(bos));
        HelloWorld.main(null);
        assertEquals("Hello World!\r\n", bos.toString());
   }
}
Funciona no Linux? :P ;)
A

Aqui eu discordo de você, Sérgio. Acho (minha opinião) que é bem mais fácil escrever código acoplado, sem separação de responsabilidades e pouco coeso sem testes unitários (os de integração não ajudam nisso, aí concordo com você).
Só o fato de você ter que pensar num nome apropriado para o método de teste, testar isoladamente um comportamento do sistema e implementar somente o necessário para que aquele pedaço de código funcione, já te ajudam a evitar o código macarrônico e acoplado.
Só uma pergunta, Sérgio: você já tentou fazer algum pet project usando TDD? (tô perguntando na boa mesmo, só pra saber se você já usou e realmente não achou legal)

G

Luca,

Erich Gamma já falava sobre isso antes, no clássico livro da GoF, ou estou enganado?

Valeu
Luiz Augusto

L

Olá

De certa forma você está certo. Mas acho que talvez eles não tenham sido muito enfáticos (estou chutando porque não me lembro).

No meu caso só tive contato com o livro do GOF (de 1995) quando foi lançado o CD (datado de 1998 ). Não me lembro o ano que comprei mas foi bem depois do livro ter se tornado clássico. Aliás, foi tão depois, que já havia até edição traduzida para o português e me lembro que comprei o CD porque custava muito mais barato do que a edição nacional. Então no meu caso o Peter Coad foi minha primeira fonte e confesso que até estranhei a insistência dele com este assunto pois dedicou a ele um capítulo inteiro do seu livro Java Design.

[]s
Luca

S

Não. Não tenho como dar minha opinião sobre mais esse método de desenvolvimento de software. Agora existe TDD, que obviamente vai depender de testes unitários e existe um sistema qualquer que está sendo feito utilizando a metodologia XXX != TDD que pode ter ou não testes unitários.

Existe algumas classes que gritam por testes unitários. Outras não. Acho testes unitários uma bela ferramenta de testes, mas não é o Santo Gral da programação. Se vc utiliza testes unitários e a luz vermelha está acendendo muito, então procure aprender o porque do erro, o que vc fez errado e como poderia ter evitado isso. Talvez assim seja uma bela ferramenta de aprendizado.

Agora se vc utiliza testes unitários, seu código tem qualidade e vc está feliz e produtivo com isso, então testes unitários é muito bom, para vc.

L

saoj:

Não é que as pessoas não entendem os defeitos de herança por causa de testes unitários. Mas sim que testes unitários colaboram para que as pessoas reneguem e não entendam herança. Se vc está começando agora a programar OO, testes unitários poderão lhe dar o ilusório conforto de que vc está fazendo a coisa direito. A mensagem parece ser essa: “Programe como quiser, faça uma arquitetura bem interdependente e enroscada, onde tudo depende de tudo. E se der verdinho parabéns!”

Que enorme besteira isso. Herança atrapalha em absolutamente nada para escrever testes unitários, pois você estará verificando uma unidade por vez. O fato de existirem um zilhão de classes filhas não interfere em nada. Sistemas com boa cobertura de testes costuma abusar menos de herança pois ela aumenta em muito o acoplamento da classes do sistema.

Eu pessoalmente acho que herança é um daqueles conceitos de OO que simplesmente não deu certo na prática, é uma idéia falha que só leva a designs furados. O próprio Gosling mesmo disse que teria tirado herança do Java se tivesse achado na época uma solução razoável para se usar apenas composição.

Quase sempre que eu vejo código usando herança é síndrome do programador folgado que prefere usar herança para entubar métodos auxiliares para economizar no teclado. O indivíduo cria classes inchadas sem nenhum desacoplamento claro de qual objeto faz o que.

Herança é forte sintoma de incapacidade de separar as responsabilidades dos objetos de maneira fina. Herança quase sempre é desculpa para fazer POG.

J

saoj:
A mensagem parece ser essa: “Programe como quiser, faça uma arquitetura bem interdependente e enroscada, onde tudo depende de tudo. E se der verdinho parabéns!”

Você está um pouco equivocado… testes unitários contribuem justamente no sentido contrário de que você afirmou.

C

Essa analogia as rodinhas de bicicleta implica que existe alguma vantagem em andar sem elas. Numa bicicleta normal, existem diversas vantagens (maior mobilidade, flexibilidade nas manobras, nao fazer papel de maneh quando vc ja passou dos 16 anos, etc). Eu nao vejo absolutamente nenhuma vantagem em escrever codigo fora do TDD alem do bom e velho “so pra dizer que eh macho”. Cite algumas?

S

Joshua Bloch usou bastante herança para fazer as collections. Usou interface tb, e da maneira correta é claro, mas fez uso extensivo de herança quando poderia ter optado por composição.

http://java.sun.com/javase/6/docs/api/java/util/LinkedHashSet.html

Que eu saiba todo objeto em Java herda de Object, logo herança é um conceito fundamental e essencial da linguagem Java.

Se eu tenho uma classe com 100 métodos e eu quero modificar o comportamento de um método, eu não quero ter que usar composição e delegar 99 métodos. É muita verbosidade em troca de ser puritano, sem falar de classes a mais (FruitImpl, Fruit, Apple). Até mesmo os criadores do Java optaram por herdar de Object ao invés de uma interface Object. Pensem bem como seria a linguagem se tivéssemos uma interface Object e a cada novo objeto tivéssemos obrigatoriamente que implementar hashCode, toString, equals, etc?

De qualquer forma, via composição + interfaces (digitando pra kacete) ou herança (digitando pouco) é possível estender as características de um objeto sem quebrá-lo, isto é, é possível criar novas funcionalidades (novo objeto ou método subescrito) sem distruir o objeto que ficou embaixo (herança) ou dentro (composição).

Mas concordo se vc tem um objeto A, e se por algum motivo justificável vc vai modificar agressivamente o código dos métodos já prontos de A, durante a vida do projeto, então seria bom ter testes unitários para A.

Eu geralmente programo um método uma única vez, faço ele funcionar como eu quero e depois não mexo mais nele, ou se mexo é para fazer um ajuste fino onde qualquer pessoa normal e minimamente atenta não vai fazer cagada. Até onde eu sei, um IF não exige replicação de código, não é POG, nem pecado.

public String myMethod(String p) {

    String res = null;

    // um monte de código aqui que vc não vai mexer...

    if (nova condição) {

      // faça uma coisa nova aqui...

    } else {

      // deixe aqui o código que já estava funcionando...

    }

    // um monte de código aqui que vc não vai mexer...

    return res;

}

A nova funcionalidade pode até não funcionar (claro, pois vc está codificando ela agora), mas o que já funcionava pode e tem que continuar funcionando.

Um caso que pede testes unitários é quando o método myMethod implementa um algoritmo mais ou menos complexo e vc vai modificar agressivamente esse algoritmo (pode até refaze-lo do zero) e não quer quebrar os contratos/output desse algortimo. Ex: BufferUtils, ByteUtils, etc. Nesses casos, achar que vai resolver o problema com um IF do tipo acima é ilusão. Mas essas classes são exceção e não regra.

É claro que o IF a cima só vai ser feito se a possibilidade de hierarquia (via herança ou composição) for descartada. Ninguém deve fazer algo assim: (erro clássico)

public void coma() {

    if (sou cachorro?) {

        // coma como um cachorro

     } else if (sou gato?) {

       // coma como um gato

     } else {

       // coma como um animal

     }
}

Será que as pessoas estão com medo de usar herança, com preguiça de usar composição, e estão programando como o exemplo acima e usando testes unitários como muleta? Sinceramente acho que não, mas talvez os conceitos de hierarquia e separação de responsabilidades não estão sendo entendidos como deveriam. É o caso de se ficar vigilante para não adentrar esse caminho, com ou sem testes unitários…

L

Sim, todo programador Java iniciante sabe disso, mas e daí?

Primeiro, porque você tem um ÚNICA classe com cem, CEM!, métodos? Isso é design ruim, e provavelmente você não está utilizando herança, nem composição, nem orientação a objetos. Provavelmente é comportamento pra ser quebrado em algumas outras classes.

Segundo, quem disse que você TEM que expor todos os métodos da classe que está delegando? A beleza da OO é o encapsulamento, e se você repassa todos os métodos, existe alguma coisa furada.

Terceiro, o que a quantidade de classes influencia na qualidade do código? Se for necessário ter mais classes para melhorar o design, que seja feito.

Quarto, o que FrutaImpl faz? Esse é o problema! Você está querendo fazer composição, mas pensando em herança! Aí é claro, isso é POG! Quer ver um exemplo bom de composição? Digamos que eu queira saber valores nutricionais da fruta, cujo cálculo é “complicado” (na realidade, não tenho a mínima idéia se é isso ou não). Eu criaria uma interface InformacaoNutricional que qualquer fruta pudesse ter como referência (ou seja, composição), e poderia criar mais de uma classe para essa essa interface se houver diferentes estratégias (pattern?) de implementação. A Fruta poderia também ter outras interfaces para outros conceitos. Ou seja, não estou usando uma classe sem sentido como FrutaImpl, onde eu iria socar qualquer coisa lá dentro, estou usando composição de maneira correta.

Isso não acontece de verdade, vide explicação anterior.

Porque complicar o descomplicado? Afinal, como é que eu diferencio um código em que se modifica “agressivamente”, de um outro em que se modifica “calmamente”? Melhor mesmo é parar de discutir besteiras e fazer testes unitários em todos os trechos de código possíveis.

saoj:
Eu geralmente programo um método uma única vez, faço ele funcionar como eu quero e depois não mexo mais nele, ou se mexo é para fazer um ajuste fino onde qualquer pessoa normal e minimamente atenta não vai fazer cagada. Até onde eu sei, um IF não exige replicação de código, não é POG, nem pecado.

public String myMethod(String p) {

    String res = null;

    // um monte de código aqui que vc não vai mexer...

    if (nova condição) {

      // faça uma coisa nova aqui...

    } else {

      // deixe aqui o código que já estava funcionando...

    }

    // um monte de código aqui que vc não vai mexer...

    return res;

}

A nova funcionalidade pode até não funcionar (claro, pois vc está codificando ela agora), mas o que já funcionava pode e tem que continuar funcionando.

Primeiro (novamente!), quem disse que o que você quer agora será a mesma coisa que você irá querer amanhã? Quem disse que um “ajuste fino” não vai quebrar outras partes do sistema? Ninguém consegue prever ao certo se determinada modificação vai trazer efeitos colaterais ou não, independente do tamanho da modificação. Isso não existe, é desculpa esfarrapada de programador preguiçoso.

Segundo, aquele if do código é pecado SIM! Quem te disse que todas as partes do sistema que utilizavam o trecho de código antigo, vai continuar usando? Isso só aconteceria se expressão dentro do if estivesse correto, cuja prova só seria possível se fosse feito testes unitários.

Besteira, não existe o conceito de “modificação agressiva”, já explicado anteriormente.

saoj:
É claro que o IF a cima só vai ser feito se a possibilidade de hierarquia (via herança ou composição) for descartada. Ninguém deve fazer algo assim: (erro clássico)

public void coma() {

    if (sou cachorro?) {

        // coma como um cachorro

     } else if (sou gato?) {

       // coma como um gato

     } else {

       // coma como um animal

     }
}

Não existe diferenças conceituais entre o primeiro e o segundo exemplo com ifs. Mas você está dizendo que um é bom e o outro não. Por quê?

saoj:
Será que as pessoas estão com medo de usar herança, com preguiça de usar composição, e estão programando como o exemplo acima e usando testes unitários como muleta? Sinceramente acho que não, mas talvez os conceitos de hierarquia e separação de responsabilidades não estão sendo entendidos como deveriam. É o caso de se ficar vigilante para não adentrar esse caminho, com ou sem testes unitários…

Também acho que as pessoas não estão com medo. É questão de inteligência mesmo. Separação de responsabilidades de faz com composição ou com herança. A diferença é que, com composição, a configuração pode ser feita em runtime.

Pra finalizar, não é questão de masculinidade. Fazemos testes unitários por puro pragmatismo.

S

Respeito inteiramente sua posição, mas gostaria de expressar meu completo desacordo com praticamente tudo que vc falou.

E o Joshua Bloch deve ser maluco por ter usado herança e não composição nas Collections. (http://java.sun.com/javase/6/docs/api/java/util/LinkedHashSet.html)

Só para explicar o FruitImpl, que acho que vc não entendeu:

public interface Object {

   public String toString();

   public int hashCode();

   // outros aqui...

}

public class ObjectImpl implements Object {

    public String toString() {
        // implementação default de todo objeto..
    }

    pubilc int hashCode() {
        // implementação default de todo objeto...
    }

    // outras implementações default aqui...
}

public class MyClass implements Object {

    private final Object obj; // composição

    public MyClass() {

        this.obj = new ObjectImpl();

    }

    public int hashCode() {
         return this.obj.hashCode(); // delegando...
    }

    public String toString() {
        // minha implementação, diferente de ObjectImpl...
        return "MyClass: " + obj.toString();
     }
}

É assim que vcs gostariam que Java fosse? Sem herança, com tudo baseado em composição e interfaces?

Pensei nesse código rapidamente, logo se houver alguma coisa errada por favor corrigia-o, para o bem do debate. Mas corrigia-o com código e não com palavras, pois o código deixa menos espaço para interpretações.

Ok, cem métodos é muito. Foi apenas um número solto, ok? Então pegue o java.util.Map, que tem mais que 10 métodos. Eu (e parece que nem o Joshua Bloch) não quero ter que reescrever e delegar 15 métodos. É tedioso e error-prone. Veja o exemplo bastante concreto de código acima e tire suas próprias conclusões. E sim, composição com interfaces é isso. Java não é Ruby com duck typing. Tem que implementar todos os métodos da interface, se não não compila. :wink:

Tem certeza? O primeiro IF é um IF lógico e nada mais. IF’s não são pecado, ou vcs querem tb remover o IF da linguagem? Já o segundo está assassinando claramente o polimorfismo, um erro clássico de quem está começando com programação OO ou de quem está programando muito rápido sem tomar cuidado com a arquitetura.

Trocar polimorfismo por if (obj instanceof Dog) é um erro muito feio que todo programador sincério (sincero + sério) vai admitir que cometeu no passado.

P
  1. Herança, assim como qualquer outra coisa, tem seus usos mas é amplamente exagerada sem motivo.

  2. Você não precisa de herança -como em java- para ter OO. Linguagens OO baseadas em protótipos basicamente clonam o objeto original. Da mesma forma linguagens como Ruby permitem que reuso de código seja feito através de mixins, você não precisa usar herança para quase nada e um subset de Ruby que não incluísse herança continuaria sendo OO.

  3. Heranças atrapalham testes da mesma maneira que composição atrapalha: quando se cria acomplamento com classes difíceis de serem testadas. O resto escrito neste tópico é FUD

  4. Você não precisa de herança para ter polimorfismo.

S

Vou tentar trazer o tópico de volta ao assunto original, começando do ponto onde ele começou a desvirtuar…

Sérgio, você pode explicar isso um pouco melhor? Eu uso TDD e testes unitários há alguns anos e não sinto isso. Quando programo eu sempre tento deixar as coisas fáceis de serem testadas, mas não lembro de ter abolido herança para isso.

Programar com testes unitários me faz pensar um pouco diferente. Eu sempre assumo que um método que eu fiz pode ser repensado a qualquer momento e por qualquer pessoa. Novas regras de negócio podem surgir, restrições de performance podem surgir, uma idéia para melhorar o design pode surgir. Neste caso meus testes ajudam a especificar e assegurar a responsabilidade de cada método, já que eu não confio na minha memória depois que alguns dias e algumas centenas de código já passaram na minha mão. E se for outro programador que estiver alterando o código, quero que ele tenha esta mesma segurança que eu.

S

Já ouvi diversas vezes por aí que herança deve ser evitada pois prejudica os testes unitários…

Colocando dessa maneira não tenho como discordar de vc. Se vc prevê e deseja tanta flexibilidade assim com o seu código e se vc tem certeza que os seus testes unitários vão garantir isso, então tudo bem. Com tanta flexibilidade assim, logo vc vai querer mudar a assinatura do método e terá que refazer também o teste unitário. E quem testa o teste unitário? Eu, pessoalmente, não gosto dessa metodologia de flexibilidade total, muda tudo, refaz tudo, etc. Eu vejo um sistema ou código como coisa séria, principalmente se ele está funcionando. Sair alterando tudo a torto e a direito exige uma ótima razão de ser. As regras do negócio não mudam a torto e a direito. Geralmente elas são evoluidas para acomodar novas funcionalidades e necessidades. Não são refeitas do zero, pelo menos não com essa frequência toda que vc sugere para justificar testes unitários. Apenas minha opinião. Há casos e casos, pessoas e pessoas, projetos e projetos, linguagens e linguagens, etc.

Na verdade, se rever a minha primeira mensagem, verá que eu entrei na discussão não para debater se testes unitários é bom ou ruim. Se vc o utiliza dessa maneira é claro que é bom. TDD é totalmente válido. O que eu discordei, e continuo discordando, é da afirmação de que não seria possível evoluir um sistema adicionando novas funcionalidades sem fazer uso de testes unitários. É sim, plenamente possível, estender sem quebrar um sistema OO minimamente bem-feito, e não deve ser os testes unitários que vão te garantir isso, mas um bom entendimento de herança, separação de responsabilidaes, polimorfismo, lógica básica (IF-THEN-ELSE) e disciplina. Pelo menos em Java…

S

Justamente porque também vejo código como coisa séria é que prefiro contar com o máximo de testes possível. Não pense no tamanho ou frequencia das mudanças. A questão é que a probabilidade de que um método nunca mude é bem baixa. Então por que não escrever alguns testes para ajudar se/quando essa hora chegar? Não é porque as mudanças são pequenas ou infrequentes que devemos tratá-las como menos importantes. Por isso em boa parte do tempo eu concordo com o Uncle Bob:

Quem afirmou isso? É claro que é possível evoluir um sistema sem testes unitários. Afinal, é assim que a maioria dos sistemas ainda são tratados. Também é óbvio que um sistema bem-feito é mais fácil de se manter. Assim como é óbvio que os testes unitários não resolvem 100% dos problemas na hora de dar manutenção num sistema.

A questão é que um conjunto de testes bem feito ajuda MUITO. E aqui não falo apenas de testes de unidade, embora eles com certeza fazem parte da “safety net” que todo programador deveria tentar construir quando está programando.

C

saoj:
s4nchez:

Sérgio, você pode explicar isso um pouco melhor? Eu uso TDD e testes unitários há alguns anos e não sinto isso. Quando programo eu sempre tento deixar as coisas fáceis de serem testadas, mas não lembro de ter abolido herança para isso.

Já ouvi diversas vezes por aí que herança deve ser evitada pois prejudica os testes unitários…

Eu tambem já ouvi diversas vezes por aí que sair de casa a noite deveria ser evitado pq tem lobisomem na rua.

Pare com o achismo.

P

A resposta é simples: um projeto do zero é dificilimo de se inserir testes unitarios por causa do acoplamento entre as classes ja existentes.

Acho que o Sergio e pessoal do menta passou por isso: alguns comecaram a colocar testes unitarios la, e perceberam que eh uma tarefa quase impossivel. Foi o mesmo quando a gente fez o vraptor1 e quis fazer testes unitarios DEPOIS de ter feito muito codigo. Testes unitarios sao impossiveis quando voce nao tem uma “unidade”.

É normal. Todo mundo passa por isso. Depois que usa testes unitarios num projeto do zero, é paixao a segunda vista!

Repare que todo mundo passou pelo mesmo efeito quando viu OO pela primeira vez: eu detestei. Quando vi que deveria usar interface em vez de heranca, tambem detestei a primeira vez. Pra mim isso é fase: todo programador bom passa pela epoca de odio ao teste unitario assim como odiou quando teve de parar de escrever arquivos de 10 mil linhas de codigo. Vamos sempre avancando.

L

Qual o problema de arquivos de 10mil linhas? Não vejo problema nisso. Minhas teclas preferidas são page-up e page-down. Muito mais rápido e produtivo que ficar trocando de arquivo o tempo todo.

B

Por que testes não são populares?

1º - Não se usa por quê não se sabe usar. Onde aprender? Nunca vi cursos sobre o assunto, documentação do JUnit, por exemplo, é palpérrima. E cadê os livros que ensinam passo-a-passo? Ou melhor, cadê os livros sobre o assunto?

2º - Não se usa por quê não se sabe aplicar testes unitários na fase de manutenção de projetos. Perguntam-se “Para quê a esta altura?”, ou melhor, “como quebrar um monolito em partes manejáveis”. O princípio de um teste unitário é que há unidades a se testar. A maioria dos projeots simplesmente não tem unidades, é um spaguetti code.

Refatoração depende de testes unitário, mas como se não temos eles em primeiro lugar?

3º - Preguiça. Falta de tempo. A empresa não tem experiência, muito menos cultura. Três letrinhas e tudo mais.

L

Ola

JUnit in Action de 2004 - excelente livro

Test Driven Development: By Example de 2002

Test Driven Development: A J2EE Example de 2005

Test-Driven Development: A Practical Guide de 2003

Pragmatic Unit Testing in Java with JUnit de 2003

TDD and Acceptance TDD for Java Developers de 2007

Java Testing and Design livro grátis disponivel em PDF desde 2004

Além destes livros, há muitos tutoriais que podem ser baixados livremente como este por exemplo: http://hotwork.sourceforge.net/hotwork/manual/junit/junit-user-guide.html

Outra fonte de aprendizado podem ser as centenas de projetos open source que usam testes unitários

E a documentação do JUnit não é “palpérrima” e muito menos paupérrima

Não é por falta de documentação que não se usa.

[]s
Luca

P

Não ter testes do início não é desculpa. Como qualquer um com alguns anos no mercado 90% do que eu fiz foi dar manutenção em alo já pronto e que não tinha testes. Testes funcionam muito bem nestes cenários, inclusive facilitando a nenharia reversa de módulos cujo “dono” foi demitido.

V

Já viu quantos métodos as classes do swing tem? O problema principal é que você nunca vai desenvolver nada puro e do zero que não use algum outro código já existente. Sempre vai usar alguma coisa já existente (Swing, EJB, ou até mesmo java.lang.*). Há partes do sistema que não foi você que fez, você não pode mudar e são engessadas. E um bom programador/arquiteto deve saber como conviver com isso e lidar com limitações. Imagine implementar JPasswordField extends Object via composição com JTextField, seria loucura. Além de que isso não funcionaria porque existem muitos lugares que iriam testar se o seu JPasswordField extends Component, JComponent ou qualquer coisa assim.

Ok, você pode falar que o swing não tem uma boa arquitetura, e eu concordo contigo. Mas aí está a diferença entre o bom arquiteto e o excelente arquiteto. O bom arquiteto sabe criar um design lindo. O excelente arquiteto sabe criar um design lindo mesmo usando componentes mal-projetados.

P

Considerando que Swing é só interface gráfica -que deve ser testada mas não necessariamente com unit tests- eu não entendi o problema. De qualquer modo, criar ‘um design lindo’ vai envolver refactoring logo a aplicação mal-projetada vai ser consertada eventualmente.

P

Os argumentos usados em discussões como esta me levam a repetir o que sempre digo: independentemente da linguagem ser estruturada ou OO ou outra porra qualquer, “POG” não é “Programação Orientada a Gambiarras”, mas sim “Programador Orientado a Gambiarras”.

Concordo plenamente !

Inclusive seria bom que os amantes da OO evitassem argumentos que possam sugerir que o que apreciam mesmo é a POPE (Programação Orientada a Preguiça de Escrever).

V

Swing foi só um exemplo. Utilize no lugar dele qualquer outro framework mal-projetado que você seja forçado a usar sem poder alterar uma única vírgula no código-fonte deste.

P

victorwss:

Swing foi só um exemplo. Utilize no lugar dele qualquer outro framework mal-projetado que você seja forçado a usar sem poder alterar uma única vírgula no código-fonte deste.

Eu ainda não entendi o problema. Se você não pode testar o componente em si basta isolá-lo. Ao invés de fazer sua classe estender isso faça uma classe que estende e apenas delega para a sua, que contêm a lógica de fato e é bem testada. Existem dezenas de padrões e técnicas para fazer isso.

V

pcalcado:
victorwss:

Swing foi só um exemplo. Utilize no lugar dele qualquer outro framework mal-projetado que você seja forçado a usar sem poder alterar uma única vírgula no código-fonte deste.

Eu ainda não entendi o problema. Se você não pode testar o componente em si basta isolá-lo. Ao invés de fazer sua classe estender isso faça uma classe que estende e apenas delega para a sua, que contêm a lógica de fato e é bem testada. Existem dezenas de padrões e técnicas para fazer isso.

Então, é aí que está a difereneça entre o bom e o excelente arquiteto:
Criar classes com alta coesão, baixo acoplamento, POGless mesmo estando em cima de um framework ruim e limitado, e não apenas criar classes com alta coesão, baixo acoplamento e POGless. :smiley:

P

O Leonardo falou que uma classe com 100 métodos é um design ruim é deve ser evitado em primeiro lugar quando comentava sobre algum argumento de que isso prejudicaria testabilidade. Você respondeu dizendo que sistemas legados podem ter 100 métodos. Eu respondi dizendo que istemas legados devem ser refatorados e/ou isolados.

Podemos dizer então que você concorda com o Leonardo?

S

Teria que haver uma interface Component. É o mesmo caso da interface Object que eu ilustrei num post anterior, mas o seu exemplo é mais um que diz como seria ingrata a vida do programador Java sem herança. Acho que a questão se herança é importante ou não já foi esclarecida. Herança é muito importante na linguagem Java e se vc não acredita em mim pelo menos acredite na implementação do Joshua Bloch de collections, que utilizou herança quando poderia ter utilizado interfaces + composição + delegação.

Acho que vc deve preferir composição ao invés de herança quando não há hierarquia, quando vc não vai precisar passar o filho como pai para sistemas legados ou outros métodos do seu sistema que esperam o pai e podem fazer uso de polimorfismo recebendo o filho.

Um PastorAlemão conter Cachorro ao invés de herdar Cachorro me parece totalmente errado. PastorAlemão é um Cachorro e muito provavelmente vc vai querer passá-lo para métodos que recebem Cachorro e usam polimorfismo. Isso em Java, onde o tipo é fundamental. Em Ruby seria outra história…

Agora se existe uma interface Cachorro, como existe uma interface java.util.Set, vc pode partir para composição + interface + delegação, que vc irá obter o mesmo resultado de herança. A única diferença, ao meu ver, é que vc vai ficar com muitas linhas a mais e repetitivas na sua classe, implementando novamente todos os métodos da interface e fazendo a delegação para o objeto possuído. Não entendo se há alguma vantagem de eliminar herança dessa maneira a não ser pela retórica de que herança não vai bem com testes unitários. Se houvesse o Joshua Bloch teria feito isso aqui http://java.sun.com/javase/6/docs/api/java/util/LinkedHashSet.html, mas ele preferiu herdar de HashSet, sem falar de AbstractSet e AbstractCollection.

Acho que se Object é uma classe concreta e não uma interface, onde todos os outros objetos obrigatoriamente herdam de Object, então ficar evitando herança quando há claramente uma relação de hierarquia é non-sense.

Mas isso não tem muito haver com o tema original que é por que testes unitários não são populares. Eu sinceramente não sei a resposta para isso, nem se a afirmação é 100% correta. Pelo menos aqui no GUJ vejo que é bastante popular.

V

O link tá errado, arruma aí.

P

Ponto para as 10 mil linhas !

S

pinto:
não importa quem:

Acho que vc deve preferir composição ao invés de herança quando não há hierarquia, quando vc não vai precisar passar o filho como pai para sistemas legados ou outros métodos do seu sistema que esperam o pai e podem fazer uso de polimorfismo recebendo o filho.

Ponto para as 10 mil linhas !

Nesse caso vc não está obrigado a implementar todos os métodos do objeto possuído, pois vc não está implementando uma interface, apenas usando composição porque não há uma relação de hierarquia.

Qualidade não tem nada haver com quantitidade. Se vc tem uma classe com mil linhas e o código está bem feito, who cares? Pra isso que existe IDE e os shortcuts do teclado.

Agora se vc tem uma classe com 10 mil linhas repetitivas, fazendo delegação para os métodos de uma interface, então é tedioso, error-prone e feio. As pessoas reclamam de getters e setters, o que dirá disso. Código tb deve ser bonito, uma forma de arte e não uma ciência exata. Se não usemos o Maker (na versão 54.3)… :stuck_out_tongue:

U

o problema não é herança, herança não é ruim, ruim é herança má utilizada …
Herança apenas para reutilização de campos/métodos causa mais problemas do que resolve.

Exemplo:
Um cliente a algum tempo atraz tinha uma classe “Usuario” e uma classe “UsuarioDependente”, a classe “UsuarioDependente” tinha apenas um campo a mais que “Usuario” que era o relacionamento com o pai.

Pensamento lógico (e errado deles), “UsuarioDependente” vai herdar de “Usuario”, isto foi uma péssima utilização de herança, e é este tipo de utilização que faz com que as pessoas assumam por regra que “herança é má”.

Por que foi errado fazer isto? Por que nem todos os métodos que esperavam um “Usuario” podiam receber um “UsuarioDependente” …
Por exemplo, considerando que houvesse um método:
public void gerarFatura(Usuario usuario)
Este não podia receber um “UsuarioDependente” pois o dependente não recebia fatura, tudo era cobrado do “Usuario”.
A solução para corrigir este problema se não me engano foi criar um “UsuarioBase” e extender as duas classes deste.
Por que um “UsuarioDependente” não é um “Usuario”, pois usuário tem comportamentos que os dependentes não possuem, como por exemplo, pagar a conta :smiley:

Herança quando usada nos casos “é um”, não tem problema algum, mas estes são poucos casos na grande maior parte dos sistemas que eu ja vi até hoje.
Em qualquer situação diferente de “é um”, usar herança é errado e vai te trazer problemas, então em todas estas situações, não use herança, use composição.
E se você perceber que não usou herança nenhuma vez (no seu modelo, herdar de Object ou outras classes padrão do java não conta para o que eu estou descrevendo), não esquenta a cabeça, é provavel que não existisse mesmo nenhum tipo de relacionamento “é um” no seu sistema, a maior parte deles não tem mesmo.
Mas se existir um “é um”, use herança sem pensar duas vezes :smiley:

PS.: este port não é resposta a um post especifico desta thread, mas a quase todos, tanto os que dizem use herança para tudo quanto os que dizem nunca use herança, ambas as posições estão erradas, orientação a objetos bem utilizada ajuda muito, o problema é que a maior parte dos programadores que conheci nas empresa que trabalhei até hoje ( a maior parte não quer dizer todos) não conseguiu entender direito a OO ainda, então usa errado e tem problemas por causa disto.

V

pcalcado:
O Leonardo falou que uma classe com 100 métodos é um design ruim é deve ser evitado em primeiro lugar quando comentava sobre algum argumento de que isso prejudicaria testabilidade. Você respondeu dizendo que sistemas legados podem ter 100 métodos. Eu respondi dizendo que istemas legados devem ser refatorados e/ou isolados.

Podemos dizer então que você concorda com o Leonardo?

Parcialmente. Eu estava questionando justamente um trecho do que ele escreveu.

E eu respondi basicamente que pode acontecer em sistemas onde é obrigatório o uso de algum tipo de framework mal projetado (seja legado ou não). E mesmo assim JPasswordField extends JTextField extends JTextComponent extends JComponent extends Container extends Component extends Object. Ou seja, há um uso grande de herança. Obviamente, há falhas de projeto nestas classes intermediárias (em especial Component, Container e JComponent, que são as principais culpadas por existir centenas de métodos). Essas três pode ser o caso onde não há nem herança, nem composição e nem OO (daí eu concordaria com ele), MAS quanto a JPasswordField, JTextField e JTextComponent, sem dúvida há um uso propício de herança. Basicamente, o problema é que você é obrigado a herdar esses métodos (mesmo se não os quiser), e EU não posso refatorar o swing para eliminar isso, ou seja, o framework ruim me forçou a herdar um caminhão de métodos.

No final crio a minha classe que herda de JPasswordField. EU estou usando a herança de forma propícia, MAS a minha classe tem centenas de métodos (porque estes foram herdados). E isto não é um design ruim que é minha culpa, e sim culpa do framework (no caso, neste exemplo, o swing e o AWT).

Quanto ao resto do post dele, não comentei nada.

V

Concordo quase completamente. O único ponto que discordo é em relação a “(no seu modelo, herdar de Object ou outras classes padrão do java não conta para o que eu estou descrevendo)”. Já vi gente criando heranças absurdas como MinhaData extends java.util.Date.

O caso clássico de mal uso de herança é o infame java.sql.Date extends java.util.Date.

R

Shoes nao entendi muito bem prq herança atrapalharia em testes por qual motivo?
No livro design Patterns (Head First) eles recomendam que de prioridade a composição em relação a heranca depois que li o captiulo abriu a minha mente em utilizar Herança

P



O objetivo (admito que não ficou claro…) do meu comentário foi comparar a grande complexidade da OO com a “extensa” simplicidade da programação estruturada (que foi a minha escola na TI). Por isso a comparação entre o seu parágrafo e as 10 mil linhas.
Aplicar corretamente a OO é tão difícil quanto formar um consenso sobre o que é correto se fazer em OO.
Realmente OO não é pra qualquer um…

Seria bom se mais gente pensasse assim.

Boa… :lol: :lol: :lol:

U

OO é simples, as pessoas é que tem mania de complicar as coisas :smiley:

L

ramilani12:
pcalcado:

3) Heranças atrapalham testes da mesma maneira que composição atrapalha: quando se cria acomplamento com classes difíceis de serem testadas. O resto escrito neste tópico é FUD

Shoes nao entendi muito bem prq herança atrapalharia em testes por qual motivo?
No livro design Patterns (Head First) eles recomendam que de prioridade a composição em relação a heranca depois que li o captiulo abriu a minha mente em utilizar Herança

O problema de herança com testes é que você não tem como testar sua classe isoladamente, tem que levar de brinde todo comportamento dos parentes.

Herança é uma bomba quando você precisa alterar o comportamento de uma classe com várias filhas.

P

urubatan:
pinto:

O objetivo (admito que não ficou claro…) do meu comentário foi comparar a grande complexidade da OO com a “extensa” simplicidade da programação estruturada (que foi a minha escola na TI). Por isso a comparação entre o seu parágrafo e as 10 mil linhas.
Aplicar corretamente a OO é tão difícil quanto formar um consenso sobre o que é correto se fazer em OO.
Realmente OO não é pra qualquer um…

OO é simples, as pessoas é que tem mania de complicar as coisas :smiley:

Excelente !

U

louds:

O problema de herança com testes é que você não tem como testar sua classe isoladamente, tem que levar de brinde todo comportamento dos parentes.

Herança é uma bomba quando você precisa alterar o comportamento de uma classe com várias filhas.


Louds, o esquema é que quando herança é utilizada corretamente, o comportamento teria que ser replicado de qualquer forma …
Ja se a herança foi utilizada só para “copiar métodos e atributos”, ai sim, uma alteração ferra com tudo …

P

Se fosse vivo, provavelmente Einstein diria que a física quântica é simples, e certamente eu discordaria…

Não creio que se possa qualificar como simples algo que a maior parte das pessoas leva bastante tempo para entender e usar corretamente.

Eu mesmo não domino completamente OO, o que me faz sofrer por causa da minha mania de fazer as coisas da maneira mais correta possível.

P

Sergio, usar heranca eh tao ruim quanto blocos de catch vazios, instanceofs em vez de polimorfismo, switches, metodos longos, classes so com getters e setters, etc…

Detesto ser repetitivo, mas vamos la:

A vida seria maravilhosa… como disse o Gosling uma vez quando falou que uma das coisas que mudaria no java era tirar o EXTENDS.

PastorAlemao realmente é um Cachorro. Voce DEVE usar o relacionamento IS A pra isso. Use entao INTERFACES. Cachorro é uma interface. Voce faz composicao com uma classe chamada DadosDeCachorro, nao com Cachorro, nem sei de onde voce tirou isso.

AbstractSet e AbstractCollection sao BEM diferentes… sao template methods, apesar de que os mais xiitas iriam falar pra voce nao usar isso tambem. LinkedHashSet teve de estender HashSet porque ja tinha gente que nao manjava direito de OO recebendo HashSet como argumento em vez de puramente Set.

Ja que voce gosta tanto de citar o Joshua Bloch, aqui vai algumas:

Rather than subclassing, just use pure interfaces.
It’s not so much that class inheritance is particularly bad. It just
has problems
- James Gosling, pai do Java

http://www.artima.com/intv/gosling3P.html

Favor object composition over class inheritance
- Erich Gamma, Design patterns

Item 14: Favor composition over inheritance
- Joshua Bloch, Effective Java

Alias, nesse capitulo, Joshua mostra EXATAMENTE como é pessimo estender… collections!!! Bem o exemplo que voce gosta de dar aqui… mostra como resolver com composicao e explica claramente o porque… vale a pena voce dar uma lida

S

Bom, talvez o pessoal devesse ler mais Nietzsche. Joshua Bloch pregou que herança é lixo mais usou bastante herança nas collections. O código das bibliotecas Java está cheio de herança por todos os lados, a começar pelo implícito extends Object, mas Deus (Gosling) recita que extends é evil e o rebanho se curva em reverência. As pessoas reclamam de getters e setters, exaltam o attr_accessor do Ruby, mas não se incomodariam de usar composição + interfaces, pois julgam assim serem merecedores da terra.

Vamos abolir o uso de herança e usar interfaces + composição pra tudo:

public interface Object {

   public String toString();

   public int hashCode();

   // outros aqui...

}

public class ObjectImpl implements Object {

    public String toString() {
        // implementação default de todo objeto..
    }

    pubilc int hashCode() {
        // implementação default de todo objeto...
    }

    // outras implementações default aqui...
}

public class MyClass implements Object {

    private final Object obj; // composição

    public MyClass() {

        this.obj = new ObjectImpl();

    }

    public int hashCode() {
         return this.obj.hashCode(); // delegando...
    }

    public String toString() {
        // minha implementação, diferente de ObjectImpl...
        return "MyClass: " + obj.toString();
     }
}

Talvez alguém mais mundano poderia oferecer uma explicação clara de o porquê ser herança um ato ignóbil.

Quem ainda faz isso? Quem ainda declara private HashSet set = new HashSet() ao invés de private Set set = new HashSet(); ? Talvez vc esteja falando de Java 1.1.1, quando não existia interface Set. Aí tudo bem, só que isso tem quase uma década e LinkedHashSet não existia em 1.1.1 (só surgiu em 1.4), então quem estava usando HashSet não queria usar LinkedHashSet, pois simplesmente não existia. Mesmo assim, não dava para ter refatorado as collections para ficar puro e certinho, sem herança? O cara que quer usar o LinkedHashSet pode e deve urgentemente refatorar de HashSet para Set. Isso sim é um erro grotesco de OO: bind to specification not to the implementation.

Talvez não tenha sido esse o motivo dele ter usado herança. Ou foi desatenção (Joshua Bloch erra?) ou foi preguiça de replicar código que nem louco só para se livrar do extends e ir para o reino dos ceús:

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {

    private static final long serialVersionUID = -2851667679971038690L;

    /**
     * Constructs a new, empty linked hash set with the specified initial
     * capacity and load factor.
     *
     * @param      initialCapacity the initial capacity of the linked hash set
     * @param      loadFactor      the load factor of the linked hash set
     * @throws     IllegalArgumentException  if the initial capacity is less
     *               than zero, or if the load factor is nonpositive
     */
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }

    /**
     * Constructs a new, empty linked hash set with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param   initialCapacity   the initial capacity of the LinkedHashSet
     * @throws  IllegalArgumentException if the initial capacity is less
     *              than zero
     */
    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }

    /**
     * Constructs a new, empty linked hash set with the default initial
     * capacity (16) and load factor (0.75).
     */
    public LinkedHashSet() {
        super(16, .75f, true);
    }

    /**
     * Constructs a new linked hash set with the same elements as the
     * specified collection.  The linked hash set is created with an initial
     * capacity sufficient to hold the elements in the specified collection
     * and the default load factor (0.75).
     *
     * @param c  the collection whose elements are to be placed into
     *           this set
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedHashSet(Collection<? extends E> c) {
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }
}

Ele podia ter tranquilamente feito assim:

public class LinkedHashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {

    private static final long serialVersionUID = -2851667679971038690L;

    private final Set mySet;

    public LinkedHashSet(int initialCapacity, float loadFactor) {
        mySet = new HashSet(initialCapacity, loadFactor, true);
    }

    public LinkedHashSet(int initialCapacity) {
        mySet = new HashSet(initialCapacity, .75f, true);
    }

    public LinkedHashSet() {
        mySet = new HashSet(16, .75f, true);
    }

    public LinkedHashSet(Collection<? extends E> c) {
        mySet = new HashSet(Math.max(2*c.size(), 11), .75f, true);
        mySet.addAll(c);
    }

    // 500 linhas aqui de métodos reimplementando todos os métodos das interfaces e
    // delegando para mySet
}

Trocaríamos Herança por composição + interfaces + um monte de repetição de código. A vida poderia ser mais pura assim, visto que nós estaríamos de acordo com as nossas entidades superiores. Mas tenho minhas dúvidas se seria mais maravilhosa…

L

Só em 2008 eu devo ter visto isso umas 10 vezes no mínimo, e não é de desenvolvedor jr não.

P

Etc., etc., etc…

Tchê Urubatan,

É por essa e outras tantas que considero OO tão complexa e consequentemente de difícil aprendizagem.
Como profissionais tão experientes e bem sucedidos podem divergir tanto?
Afinal OO pode ser considerada uma “ciência” exata, ou é mais subjetiva?
Um humilde pedido:
Vc, que é tambem experiente e bem sucedido, poderia indicar um caminho (livro, cursos, etc…) para que eu possa dominar OO e um dia talvez considera-la simples?
Por favor, me ajude!

L

Sérgio, composição só é gera toneladas de métodos de delegação para aqueles que usam uma linguagem pobre e não sabem usar os recursos dela.
Com Java já é possível resolver esse problema a anos usando, por exemplo, proxies.

O problema do LinkedHashSet é que as collections do Java nunca foram feitas para serem extendidas. O design delas é um fracasso nesse sentido.
Se elas suportassem eventos e callbacks seria trivial implementar LHS sem toneladas de delegação.

Justificar herança usando um exemplo furado é fácil.

R

Sérgio, por mais incrível que pareça, na maioria dos casos, as “boas práticas” não foram criadas simplesmente para preencher espaço, para aparecer ou para depois aparecer no fórum com uma listinha de pessoas que usam as “boas práticas” criadas por ela.

As ‘boas práticas’ geralmente são criadas quando se passa por uma situação diversas vezes (experiência) e se estuda elas (conhecimento), analizando qual é a melhor alteranativa que geralmente atende a maior parte dos casos. É claro que vai ter uma situação ou outra que é melhor fazer algo diferente, mas na média, é melhor se ater as boas práticas, geralmente os resultados são melhores. É claro que não se deve seguir cegamente tudo o que se fala, mas se 12319083791280 pessoas que são reconhecidas na comunidade estão falando bem de algo, é melhor pelo fazer uma analize séria, é provável que vá te ajudar em alguma coisa.

Note que o pessoal encheu o GUJ de posts com argumentos técnicos e você mais uma vez esta tapando os ouvidos e gritando que é complexo, é complicado, é error-prone, etc. O mesmo argumento você usa para o Hibernate, por exemplo. É um excelente ORM, facilita muito a vida, é poderoso, é usado por muitos empresas nos mais diferentes ambientes, etc. Dificilmente alguém conseguiria num projeto “normal” criar algum mecanismo de ORM melhor que o Hibernate, na média é melhor você usar ele do que criar o seu próprio ORM caseiro. Só que para usar ele você tem que gastar algumas horas para entende-lo. E por causa disso você diz que é complexo, é complicado, é error-prone, etc.
As “boas práticas” também requerem que você gaste um bom tempo para entende-las. Parece que tudo que leva mais que uma hora para aprender você acha complexo, complicado e error-prone.

T

OO é simples? Boa parte do povo que aprende essa coisa na faculdade desaprende logo que entra no mercado. Ou pior ainda é quando acha que realmente aprendeu para você acompanhar uma thread no guj que desmente tudo que você outrora acreditara. IMHO, OO não é simples, por isso as coisas se complicam (caso não seja gasto o devido esforço e paciência para aprender aplicar OO na prática!).

mas… é só desabafo :stuck_out_tongue: continuem a discussão!

F

urubatan:
louds:

O problema de herança com testes é que você não tem como testar sua classe isoladamente, tem que levar de brinde todo comportamento dos parentes.

Herança é uma bomba quando você precisa alterar o comportamento de uma classe com várias filhas.


Louds, o esquema é que quando herança é utilizada corretamente, o comportamento teria que ser replicado de qualquer forma …
Ja se a herança foi utilizada só para “copiar métodos e atributos”, ai sim, uma alteração ferra com tudo …

Perfeito. Afirmação mais sensata até agora.

S

Tem razão. Só que proxy não é algo natural dentro de Java. É um tapa-buraco. Requer interfaces (a não ser cglib) e por aí vai. Mas foi uma ótima observação. Antes do Gosling falar que Java não deveria ter extends ele deveria ter falado que Java deveria ter um esquema de proxies um pouco mais descente ou simplesmente um method_missing.

Não acredito em qualquer coisa que me dizem como se fosse uma doutrina religiosa sobrenatural e irracional do além-nada. Um bom debate não faz mal a ninguém e muito menos para quem está equivocado. Boas práticas podem se tornam más práticas, vide singleton. Seus argumentos foram fracos e carregados de emoção, que é a principal inimiga da razão. Creio que sua perfídia colaborou negativamente para o debate, mas mesmo assim parabéns, pois seus anseios foram amansados e nem foi necessário apelar para os deuses ou o sobrenatural! Pense nisso e leia um pouco de Nietzsche e Epicuro, pode te fazer bem. E lembre-se: "Desacreditar o argumentador não desacredita o seu argumento. Não é preciso ser religioso (nem do PT) para ver que atacar um amigo é imoral. Vc fala de flores, mas o seu Jardim tem ervas daninhas…

OO em teoria é extremamente simples. Já na prática não o é, pelo contrário, é bem complicado e requer muita prática para dominar. É um aprendizado constante. Quanto mais se programa, mais se aprende. Qualquer programador sinsério vai admitir que seus primeiros programas OO eram um lixo.

J

Minha contribuição:
Testes unitarios são muito uteis sim. Ponto. são a solução para os problemas do mundo? Claro que não.
Herança é bom ou ruim? Boa em alguns caso, ruins em outros. Como quase tudo na nossa area.
O problema é que as coisas não são 8 ou 80, bom ou mal, azul ou vermelho.
Sim, é possivel escrever um sistema TODO, que funcione, que seja confiavel, <coloque aqui seu requisito prefereido> sem um unico teste.
A maiora aqui faz isso todos os dias. Eu faço todos os dias.
A questão é que se me perguntassem se eu prefiro fazer com testes ou sem testes, minha resposta seria: Com Testes!!!
Pq? Alem tudo que ja foi dito nessa thread, pq ja participei de projetos com e sem testes, e senti na pele a diferença.
Usado da maneira certa, junto com outras praticas ele ajuda em muito.
Quem nunca escreveu um codigo e pensou depois “coitado de quem for ter que alterar isso!!!”?
Bom, com testes, o “coitado vai ter um ponto de partida”, os testes.
Quem faz codigo são as pesssas, pessoas produzem codigo de maneiras diferentes.
Testes fazem parte de uma cultura, de uma maneira de se fazer software. Sozinho, talvez ele mais atrapalhe do que ajude.
Cultura essa que diz que o codigo não tem dono, não existe o dono daquela “logica maluca” que so ele pode alterar.
Por isso existem testes, para se ter um ponto de partida para o entendimento do sistema, entre outras coisas.

[]´s

V

Talvez até fosse possível usar-se apenas composição e não herança. O problema é que o java foi arquitetado desta forma e não será refatorado (backward compatibility). Logo, se você for programar em java deverá usar herança. Não gosta disso? Procure outra linguagem ou aprenda a lidar com isso. Não tem jeito.

Interfaces também tem lá seus problemas (que pouca gente cita). Precisa adicionar um método na interface ou mudar a assinatura? VOCÊ VAI QUEBRAR TODAS AS IMPLEMENTAÇÕES AO FAZÊ-LO! E que tal fazer uma outra interface que herde desta primeira acrescentando os métodos novos e colocar um @Deprecated nos que devem ser removidos? VOCÊ ESTARÁ FAZENDO GAMBIARRAS, E NÃO CONSERTANDO O PROBLEMA! Aliás, existem interfaces no java que são gambiarradas desta forma.

Acho que o maior problema em java é a falta de mixins. Se houvesse mixins, delegação seria algo mais simples e mais usado, mas talvez isso pudesse trazer a herança múltipla de classes. E antes que falem, sim herança múltipla de classes também tem algum propósito válido em alguns casos.

Outro problema no java é o backward compatibility. Embora ele seja vital para o sucesso do java, ele é ao mesmo tempo um câncer. As cagadas feitas na API estão lá para sempre!

Hehe, mas o tópico já se desvirtuou totalmente aqui :smiley:

C

bocejo

J

Bom,
respondendo a pergunta original do topico, eu acho que os testes unitarios ainda não são tão populares pq ainda não se vende software se utilizando alguma cultura/metodologia que use testes.
A maioria do sistemas desenvolvidos ainda utilizam waterfall(classico ou modificado) aonde não existe a figura dos testes unitarios. Ou quando existe, vão ser feitos em uma “fase de construção de testes”, ou seja, muito codigo ja foi produzido, dificultando a utilização de testes.
Eu pelo menos, so desenvolvi usando testes unitarios em projetos que utilizaram alguma metodologia agil. Pq nesses casos os testes eram vistos como devem, sem ser a bala de prata, mas sendo muito uteis dentro da maneira de desenvolvimento proposta.

[]´s

V

jgbt:
Bom,
respondendo a pergunta original do topico, eu acho que os testes unitarios ainda não são tão populares pq ainda não se vende software se utilizando alguma cultura/metodologia que use testes.
A maioria do sistemas desenvolvidos ainda utilizam waterfall(classico ou modificado) aonde não existe a figura dos testes unitarios. Ou quando existe, vão ser feitos em uma “fase de construção de testes”, ou seja, muito codigo ja foi produzido, dificultando a utilização de testes.
Eu pelo menos, so desenvolvi usando testes unitarios em projetos que utilizaram alguma metodologia agil. Pq nesses casos os testes eram vistos como devem, sem ser a bala de prata, mas sendo muito uteis dentro da maneira de desenvolvimento proposta.

[]´s

Pois é. Imagine que você já tenha um código grande e peçam para criar o código de teste. É possível fazer isso de forma relativamente fácil? Se a resposta for não, está aí a causa deles não serem muito populares!

L

Pras consultorias é vantajoso não ter testes antes, porque o software vai dar trocendo milhoões de problemas, eles vão ficar extendendo o prazo de entrega eternamente e a consultoria continua ganhando lá R$ 150/hora por cada desenvolvedor, qual a vantagem pra eles entregam algo de qualidade dentro do prazo?

E infelizmente é esse tipo de empresa que pega a grande maioria (chute uns 90% até) dos projetos de softwares existentes no mercado.

L

Olá

Só que a resposta é sim. O próprio Mentawai criou testes assim.

Existem alguns softwares que fazem isto como o JUnit Factory por exemplo: http://www.agitar.com/

[]s
Luca

P

ramilani12:
pcalcado:

3) Heranças atrapalham testes da mesma maneira que composição atrapalha: quando se cria acomplamento com classes difíceis de serem testadas. O resto escrito neste tópico é FUD

Shoes nao entendi muito bem prq herança atrapalharia em testes por qual motivo?
No livro design Patterns (Head First) eles recomendam que de prioridade a composição em relação a heranca depois que li o captiulo abriu a minha mente em utilizar Herança

O que eu quis dizer foi que assim como composição pode atrapalhar testabilidade se mal feita a herança também. Herança por si só não atrapaha testabilidade, depende de como é feita.

Acho que você não entendeu o que eu falei. Se você não pode refatorar o framework você ainda pode isolá-lo do seu código. Tem que implementar uma interface com mil métodos? Não faça a sua classe de negócios implementar isso, crie uma classe que implementa e atende os desejos profanos do framework mas delega para a sua classe de negócios, que é bem testada e seue bons princípios. Você até pode ter uma classe de 100 métodos mas apenas como ‘cola’ entre framework e seu código.

Agora eu não entendi como este debate caiu para herança. Além do fato de que herança por si só não prejudica testabilidade (design ruim prejudica, mas até aí…) esse papo de que ‘bom design oo evita necessidade de testes’ é tão furado hoje quanto sempre foi.

Uma recomendação para quem quer avaliar seus sistemas: http://testabilityexplorer.org/

P

eh que java nao tem mixins ou algum sistema de delegates. eh um problema da linguagem mesmo.

a segunda solucao que voce apresentou eh incrivelmente mais elegante que a primeira, voce mesmo reconhece.

e nao eh repeticao de codigo, eh delegacao… voce chama repetir codigo soh porque tem de copiar a assinatura de um metodo de uma interface?

J

Luiz Aguiar:
Pras consultorias é vantajoso não ter testes antes, porque o software vai dar trocendo milhoões de problemas, eles vão ficar extendendo o prazo de entrega eternamente e a consultoria continua ganhando lá R$ 150/hora por cada desenvolvedor, qual a vantagem pra eles entregam algo de qualidade dentro do prazo?

E infelizmente é esse tipo de empresa que pega a grande maioria (chute uns 90% até) dos projetos de softwares existentes no mercado.

Exatamente isso.
Conversando com um amigo que é gerete de projetos de uma consultoria ele me disse isso. Apesar de atrasos e encomodações, os projetos eram extremamente lucrativos, por isso a consultoria continuava a vender dessa maneira.
Essa mudança na verdade deveria partr do cliente, exigindo mudanças. Mas os proprios clientes tem medo dessa mudança. Na maioria das vezes o cliente exige saber quanto o sistema vai custar e quando vai ser entregue. Mesmo sabendo que dificilmente essas metas irão ser atingidas.

[]'s

U

Paulo Silveira:
Sergio, usar heranca eh tao ruim quanto blocos de catch vazios, instanceofs em vez de polimorfismo, switches, metodos longos, classes so com getters e setters, etc…


A única coisa que eu mudaria neste teu comentário é que eu diria: usar herança errado.

B

Existe alguma linguagem (OO) cuja sintaxe dê ênfase na troca de mensagens?

Sem herança, sem classes, sem interfaces… Com duck typing, talvez. Ia ser interessante.

P

bzanchet:
Existe alguma linguagem (OO) cuja sintaxe dê ênfase na troca de mensagens?

Sem herança, sem classes, sem interfaces… Com duck typing, talvez. Ia ser interessante.

Se você não usar herança em Ruby vai ter exatamente o que precisa. Aliás, raramente se precisa de herança em algo como Ruby.

B

Mas ruby tem classes (e métodos estáticos).

Seria legal se tivesse pattern matching também. E se ‘function’ e ‘method’ e ‘def’ e ‘class’ não significassem nada de especial. Acho que um novo vocabulário faria mais bem do que mal (eg: bdd vs tdd :D) - mantem-se o termo ‘object’, reinventa-se o resto (dando ênfase em “troca de mensagens”, não em “funcionalidade”).

P

E qual o problema em ter métodos e atributos de classe já que a classe em si é um objeto?

B

Não, nenhum. Me referia ao vocabulário, apenas…

P

Eu Não entendi o ‘troca de mensagens ao inves de funcionalidade’ mas nao creio que seja dificil fazer o que voce quer com um subset do Ruby.

G

Porque excluiram meu post ???

:?:

P

[ironic]
Isso atrapalha na hora de fazer um VO e um BO?

P

gargula:
Porque excluiram meu post ???

:?:

Porque você já foi avisado dezenas de vezes. Se quer fazer propaganda da sua ferramenta predileta faça nos 15 tópicos sobre ela, não faça spam.

Criado 30 de maio de 2008
Ultima resposta 30 de jul. de 2008
Respostas 93
Participantes 32