Programação para uso futuro de interfaces

41

Eu tenho um colega sentado ao meu lado que projetou uma interface como esta:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}

O problema é que, no momento, não estamos usando esse parâmetro "end" em nenhum lugar de nosso código, ele está lá apenas porque talvez tenhamos que usá-lo em algum momento no futuro.

Estamos tentando convencê-lo de que é uma má idéia colocar parâmetros em interfaces que não são úteis no momento, mas ele continua insistindo que muito trabalho terá que ser feito se implementarmos o uso do "fim". data algum tempo depois e tem que adaptar todo o código então.

Agora, minha pergunta é: há alguma fonte que esteja lidando com um tópico como esse de gurus de codificação "respeitados" com os quais possamos vinculá-lo?

    
por sveri 11.07.2014 / 09:58
fonte

9 respostas

61

Convide-o para conhecer o YAGNI . A parte racional da página da Wikipédia pode ser particularmente interessante aqui:

According to those who advocate the YAGNI approach, the temptation to write code that is not necessary at the moment, but might be in the future, has the following disadvantages:

  • The time spent is taken from adding, testing or improving the necessary functionality.
  • The new features must be debugged, documented, and supported.
  • Any new feature imposes constraints on what can be done in the future, so an unnecessary feature may preclude needed features from being added in the future.
  • Until the feature is actually needed, it is difficult to fully define what it should do and to test it. If the new feature is not properly defined and tested, it may not work correctly, even if it eventually is needed.
  • It leads to code bloat; the software becomes larger and more complicated.
  • Unless there are specifications and some kind of revision control, the feature may not be known to programmers who could make use of it.
  • Adding the new feature may suggest other new features. If these new features are implemented as well, this could result in a snowball effect towards feature creep.

Outros argumentos possíveis:

  • “80% do custo de vida de um software vai para manutenção” . Escrever código just in time reduz o custo da manutenção: é preciso manter menos código e focar no código realmente necessário.

  • O código-fonte é escrito uma vez, mas lido dezenas de vezes. Um argumento adicional, não usado em nenhum lugar, levaria ao desperdício de tempo, entendendo por que há um argumento que não é necessário. Dado que esta é uma interface com várias implementações possíveis, torna as coisas mais difíceis.

  • Espera-se que o código-fonte seja autodocumentado. A assinatura real é enganosa, já que um leitor pensaria que end afeta o resultado ou a execução do método.

  • As pessoas que escrevem implementações concretas dessa interface podem não entender que o último argumento não deve ser usado, o que levaria a abordagens diferentes:

    1. Eu não preciso de end , então vou simplesmente ignorar seu valor,

    2. Eu não preciso de end , então vou lançar uma exceção se não for null ,

    3. Eu não preciso de end , mas tentarei usá-lo de alguma forma,

    4. Escrevo muito código que pode ser usado mais tarde quando end for necessário.

Mas saiba que seu colega pode estar certo.

Todos os pontos anteriores são baseados no fato de que a refatoração é fácil, então adicionar um argumento mais tarde não requer muito esforço. Mas esta é uma interface e, como interface, pode ser usada por várias equipes que contribuem para outras partes de seu produto. Isso significa que mudar uma interface pode ser particularmente doloroso. Nesse caso, YAGNI não se aplica aqui.

A resposta do hjk oferece uma boa solução: adicionar um método a uma interface já usada não é particularmente difícil mas, em alguns casos, também tem um custo substancial:

  • Algumas estruturas não suportam sobrecargas. Por exemplo, e se eu me lembro bem (me corrija se eu estiver errado), o WCF do .NET não suporta sobrecargas.

  • Se a interface tiver muitas implementações concretas, adicionar um método à interface exigiria passar por todas as implementações e incluir o método lá também.

por 11.07.2014 / 11:21
fonte
26

but he keeps on insisting that a lot of work will have to be done if we implement the use of "end" date some time later and have to adapt all the code then.

(algum tempo depois)

public class EventGetter implements IEventGetter {

    private static final Date IMPLIED_END_DATE_ASOF_20140711 = new Date(Long.MAX_VALUE); // ???

    @Override
    public List<FooType> getFooList(String fooName, Date start) throws Exception {
        return getFooList(fooName, start, IMPLIED_END_DATE_ASOF_20140711);
    }

    @Override
    public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception {
        // Final implementation goes here
    }
}

Isso é tudo que você precisa, sobrecarga de método. O método adicional no futuro pode ser introduzido de forma transparente, sem afetar as chamadas ao método existente.

    
por 11.07.2014 / 11:35
fonte
18

[Are there] "respected" coding gurus that we can link him to [to persuade him]?

Apelos à autoridade não são particularmente convincentes; melhor apresentar um argumento que é válido, não importa quem disse isso.

Aqui está uma reductio ad absurdum que deve persuadir ou demonstrar que o seu colega de trabalho está preso ao "estar certo", independentemente da sensibilidade:

O que você realmente precisa é

getFooList(String fooName, Date start, Date end, Date middle, 
           Date one_third, JulianDate start, JulianDate end,
           KlingonDate start, KlingonDate end)

Você nunca sabe quando terá que internacionalizar-se para Klingon, então é melhor você cuidar disso agora, porque isso exigirá muito trabalho para melhorar e os Klingons não são conhecidos por sua paciência.

    
por 11.07.2014 / 10:52
fonte
12

De uma perspectiva de engenharia de software, acredito que a solução adequada para esse tipo de problema esteja no padrão do construtor. Este é definitivamente um link dos autores do 'guru' para o seu colega link .

No padrão do construtor, o usuário cria um objeto que contém os parâmetros. Este contêiner de parâmetro será então passado para o método. Isso cuidará de qualquer extensão e sobrecarga de parâmetros que seu colega precisaria no futuro, ao mesmo tempo em que tornaria a coisa toda muito estável quando mudanças precisassem ser feitas.

Seu exemplo se tornará:

public interface IEventGetter {
    public List<FooType> getFooList(ISearchFooRequest req) {
        throws Exception;
    ....
    }
}

public interface ISearchFooRequest {
        public String getName();
        public Date getStart();
        public Date getEnd();
        public int getOffset();
        ...
    }
}

public class SearchFooRequest implements ISearchFooRequest {

    public static SearchFooRequest buildDefaultRequest(String name, Date start) {
        ...
    }

    public String getName() {...}
    public Date getStart() {...}
    ...
    public void setEnd(Date end) {...}
    public void setOffset(int offset) {...}
    ...
}
    
por 11.07.2014 / 13:15
fonte
7

Você não precisa disso agora, então não o adicione. Se você precisar mais tarde, estenda a interface:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start)
         throws Exception;
    ....

}

public interface IBoundedEventGetter extends IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}
    
por 11.07.2014 / 18:38
fonte
4

Nenhum princípio de design é absoluto, então, embora eu concorde principalmente com as outras respostas, pensei em representar o advogado do diabo e discutir algumas condições sob as quais eu consideraria aceitar a solução de seu colega:

  • Se esta é uma API pública e você espera que o recurso seja útil para desenvolvedores de terceiros, mesmo que você não o use internamente.
  • Se tiver benefícios imediatos consideráveis, não apenas futuros. Se a data final implícita for Now() , a inclusão de um parâmetro removerá um efeito colateral, o que traz benefícios para o armazenamento em cache e para o teste de unidade. Talvez isso permita uma implementação mais simples. Ou talvez seja mais consistente com outras APIs no seu código.
  • Se a sua cultura de desenvolvimento tem um histórico problemático. Se os processos ou a territorialidade tornam muito difícil mudar algo central como uma interface, então o que eu vi acontecer antes é que as pessoas implementam soluções alternativas do lado do cliente em vez de mudar a interface, então você está tentando manter uma dúzia de datas finais ad hoc filtros em vez de um. Se esse tipo de coisa acontece muito em sua empresa, faz sentido colocar um pouco mais de esforço em provas futuras. Não me entenda mal, é melhor mudar seus processos de desenvolvimento, mas normalmente é mais fácil dizer do que fazer.
Dito isto, na minha experiência, o maior corolário de YAGNI é YDKWFYN: Você não sabe de que forma você vai precisar (sim, eu acabei de inventar esse acrônimo). Mesmo que a necessidade de algum parâmetro possa ser relativamente previsível, ele pode ter a forma de um limite de página ou número de dias ou um booleano especificando o uso da data de término de uma tabela de preferências do usuário ou número de coisas.

Como você ainda não tem o requisito, não há como saber qual tipo esse parâmetro deve ser. Muitas vezes você acaba tendo uma interface inadequada que não é a melhor opção para sua necessidade, ou ter que alterá-la de qualquer maneira.

    
por 11.07.2014 / 18:54
fonte
2

Não há informações suficientes para responder a essa pergunta. Depende de qual getFooList realmente faz e como faz isso.

Aqui está um exemplo óbvio de um método que deve suportar um parâmetro adicional, usado ou não.

void CapitalizeSubstring (String capitalize_me, int start_index);

A implementação de um método que opera em uma coleção na qual você pode especificar o início, mas não o final da coleção, costuma ser tola.

Você realmente tem que olhar para o problema em si e perguntar se o parâmetro é sem sentido no contexto de toda a interface e realmente quanto peso o parâmetro adicional impõe.

    
por 11.07.2014 / 23:22
fonte
1

Eu tenho medo que seu colega possa ter um ponto muito válido. Embora sua solução não seja a melhor.

De sua interface proposta, é claro que

public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception;

está retornando instâncias encontradas dentro de um intervalo no tempo. Se os clientes atualmente não usam o parâmetro end, isso não altera o fato de eles esperarem que as instâncias sejam encontradas dentro de um intervalo no tempo. Significa apenas que atualmente todos os clientes usam intervalos abertos (do início à eternidade)

Assim, uma interface melhor seria:

public List<FooType> getFooList(String fooName, Interval interval) throws Exception;

Se você fornecer o intervalo com um método de fábrica estático:

public static Interval startingAt(Date start) { return new Interval(start, null); }

então os clientes não sentirão a necessidade de especificar um horário de término.

Ao mesmo tempo, sua interface transmite mais corretamente o que faz, pois getFooList(String, Date) não comunica que isso lida com um intervalo.

Note que minha sugestão decorre do que o método faz atualmente, não do que deveria ou poderia fazer no futuro, e como tal o princípio YAGNI (que é realmente válido) não se aplica aqui.

    
por 16.07.2014 / 01:36
fonte
0

Adicionar um parâmetro não usado é confuso. As pessoas podem chamar esse método assumindo que esse recurso funcionará.

Eu não adicionaria. É trivial adicioná-lo posteriormente usando uma refatoração e consertando mecanicamente os sites de chamadas. Em uma linguagem estaticamente tipada, isso é fácil de fazer. Não é necessário adicioná-lo ansiosamente.

Se houver uma razão para ter esse parâmetro, você pode evitar a confusão: Adicione uma declaração na implementação dessa interface para impor que esse parâmetro seja passado como o valor padrão. Se alguém acidentalmente usar esse parâmetro, pelo menos, ele notará imediatamente durante o teste que esse recurso não está implementado. Isso tira o risco de escorregar um bug para a produção.

    
por 12.07.2014 / 00:07
fonte