Estou escrevendo uma implementação Java de um jogo de cartas, então criei um tipo especial de coleção que estou chamando de zona. Todos os métodos de modificação do Java's Collection não são suportados, mas há um método na API da região move(Zone, Card)
, que move um cartão da região em questão para si (realizado por técnicas de pacote privado). Dessa forma, posso garantir que nenhum cartão seja retirado de uma zona e simplesmente desapareça; eles só podem ser movidos para outra zona.
Minha pergunta é: quão necessário é esse tipo de codificação defensiva? É "correto" e parece a prática correta, mas não é como se a API da Zona fizesse parte de alguma biblioteca pública. É só para mim, é como se eu estivesse protegendo meu código de mim mesmo quando provavelmente poderia ser mais eficiente usando apenas Coleções padrão.
Até onde devo levar essa ideia da Zona? Alguém pode me dar algum conselho sobre o quanto devo pensar em preservar os contratos nas aulas que escrevo, especialmente para aqueles que realmente não estarão disponíveis ao público?
fonte
Respostas:
Não vou abordar o problema de design - apenas a questão de fazer as coisas "corretamente" em uma API não pública.
Esse é exatamente o ponto. Talvez existam programadores que se lembram das nuances de todas as classes e métodos que eles escreveram e nunca os chamam erroneamente com o contrato errado. Eu não sou um deles. Costumo esquecer como o código que escrevi deve funcionar poucas horas depois de escrevê-lo. Depois de pensar que acertou uma vez, sua mente tenderá a mudar de direção para o problema no qual está trabalhando agora .
Você tem ferramentas para combater isso. Essas ferramentas incluem (em nenhuma ordem específica) convenções, testes de unidade e outros testes automatizados, verificação de pré-condição e documentação. Eu mesmo achei os testes de unidade inestimáveis porque ambos o forçam a pensar em como o seu contrato será usado e fornecem a documentação posteriormente sobre como a interface foi projetada.
fonte
Eu costumo seguir algumas regras simples:
IllegalArgumentException
).assert input != null
).Se um cliente estiver realmente interessado nisso, sempre encontrará uma maneira de fazer com que seu código se comporte mal. Eles sempre podem fazê-lo através da reflexão, pelo menos. Mas essa é a beleza do design por contrato . Você não aprova esse uso do seu código e, portanto, não pode garantir que ele funcione em tais cenários.
Quanto ao seu caso específico, se
Zone
não deve ser usado e / ou acessado por pessoas de fora, torne a classe privada do pacote (e possivelmentefinal
), ou preferencialmente, use as coleções que o Java já fornece. Eles são testados e você não precisa reinventar a roda. Observe que isso não impede que você use asserções em todo o código para garantir que tudo funcione conforme o esperado.fonte
A programação defensiva é uma coisa muito boa.
Até que comece a atrapalhar a escrita do código. Então não é uma coisa tão boa.
Falando um pouco mais pragmaticamente ...
Parece que você está no limite de levar as coisas longe demais. O desafio (e a resposta à sua pergunta) consiste em entender quais são as regras ou requisitos de negócios do programa.
Usando a API do jogo de cartas como exemplo, existem alguns ambientes em que tudo o que pode ser feito para evitar trapaças é crítico. Grandes quantidades de dinheiro real podem estar envolvidas; portanto, faz sentido colocar um grande número de verificações para garantir que não ocorram trapaças.
Por outro lado, você precisa manter-se atento aos princípios do SOLID, especialmente à responsabilidade única. Pedir à classe de contêiner para auditar efetivamente para onde os cartões estão indo pode ser um pouco demais. Talvez seja melhor ter uma camada de auditoria / controlador entre o contêiner do cartão e a função que recebe as solicitações de movimentação.
Relacionado a essas preocupações, você precisa entender quais componentes da sua API são expostos publicamente (e, portanto, vulneráveis) versus o que é privado e menos exposto. Não sou um defensor total de um "revestimento externo duro com um interior macio", mas o melhor retorno do seu esforço é proteger o exterior da sua API.
Não acho que o usuário final pretendido de uma biblioteca seja tão crítico quanto uma determinação sobre a quantidade de programação defensiva que você implementa. Mesmo com os módulos que eu escrevo para meu próprio uso, ainda coloco uma medida de verificação para garantir que meu futuro não cometa algum erro inadvertido ao chamar a biblioteca.
fonte
A codificação defensiva não é apenas uma boa ideia para o código público. É uma ótima idéia para qualquer código que não seja imediatamente jogado fora. Claro, você sabe como deve ser chamado agora , mas não tem ideia de quão bem se lembrará disso daqui a seis meses quando voltar ao projeto.
A sintaxe básica do Java oferece muita defesa incorporada em comparação com uma linguagem interpretada ou de nível inferior, como C ou Javascript, respectivamente. Supondo que você nomeie seus métodos claramente e não tenha "seqüenciamento de métodos" externo, provavelmente poderá especificar argumentos como um tipo de dados correto e incluir um comportamento sensível se os dados digitados corretamente ainda puderem ser inválidos.
(De um lado, se as Cartas sempre tiverem que estar na zona, acho que você fica melhor ao fazer com que todas as cartas em jogo sejam referenciadas por uma coleção global para o seu objeto Jogo, e a Zona seja uma propriedade de Mas como não sei o que suas Zonas fazem além de guardar as cartas, é difícil saber se isso é apropriado.)
fonte
CardDescriptor
que contenha uma carta, sua localização, status voltado para cima / baixo ou até rotação para jogos que se importam com isso. Todas essas são propriedades mutáveis que não alteram a identidade de um cartão.Primeiro, crie uma classe que mantenha uma lista de zonas para que você não perca uma zona ou as cartas nela. Você pode então verificar se há uma transferência na sua ZoneList. Essa classe provavelmente será uma espécie de singleton, pois você precisará apenas de uma instância, mas poderá querer conjuntos de zonas mais tarde, portanto, mantenha suas opções em aberto.
Segundo, não tenha o Zone ou o ZoneList implementado Collection ou qualquer outra coisa, a menos que você precise. Ou seja, se uma zona ou zonelist será passado para algo que espera uma coleção, em seguida, implementá-lo. Você pode desativar vários métodos fazendo com que eles gerem uma exceção (UnimplementedException, ou algo assim) ou fazendo com que eles simplesmente não façam nada. (Pense bem antes de usar a segunda opção. Se você fizer isso porque é fácil, descobrirá que está perdendo bugs que poderia ter detectado desde o início.)
Existem perguntas reais sobre o que é "correto". Mas depois de descobrir o que é, você vai querer fazer as coisas dessa maneira. Em dois anos, você terá esquecido tudo isso e, se tentar usar o código, ficará muito irritado com o cara que o escreveu de maneira tão contra-intuitiva e não explicou nada.
fonte
A codificação defensiva no design da API geralmente é sobre a validação de entrada e a seleção cuidadosa de um mecanismo adequado de tratamento de erros. Coisas que outras respostas mencionam também merecem destaque.
Na verdade, esse não é o seu exemplo. Você está limitando sua superfície da API, por um motivo muito específico. Como o GlenH7 menciona, quando o conjunto de cartas deve ser usado em um jogo real, com um baralho ('usado' e 'não utilizado'), uma mesa e mãos, por exemplo, você definitivamente deseja colocar cheques para garantir que cada O cartão do conjunto está presente uma vez e apenas uma vez.
Que você projetou isso com "zonas" é uma escolha arbitrária. Dependendo da implementação (uma zona pode ser apenas uma mão, um deck ou uma tabela no exemplo acima), pode muito bem ser um projeto completo.
No entanto, essa implementação soa como um tipo derivado de um
Collection<Card>
conjunto de cartões mais parecido, com uma API menos restritiva. Por exemplo, quando você deseja criar uma calculadora de valor de mão, ou uma IA, você certamente quer ser livre para escolher qual e quantas cartas de cada iterar.Portanto, é bom expor uma API tão restritiva, se o único objetivo dessa API é garantir que cada placa esteja sempre em uma zona.
fonte