Question

Un projet m'a été confié afin de développer un ensemble de classes servant d'interface avec un système de stockage. Une exigence est que la classe prenne en charge une méthode get avec la signature suivante:

public CustomObject get(String key, Date ifModifiedSince)

En gros, la méthode est supposée renvoyer le CustomObject associé à la clé si et seulement si l'objet a été modifié après ifModifiedSince . Si le système de stockage ne contient pas la clé , la méthode doit alors renvoyer null.

Mon problème est le suivant:

Comment gérer le scénario dans lequel la clé existe mais où l'objet n'a pas été modifié?

Cela est important car certaines applications utilisant cette classe seront des services Web et des applications Web. Ces applications devront savoir s'il faut renvoyer un 404 (non trouvé), 304 (non modifié) ou 200 (OK, voici les données).

Les solutions que je considère sont les suivantes:

  1. Lance une exception personnalisée lorsque le système de stockage ne contient pas le clé
  2. Lance une exception personnalisée lorsque le ifModifiedSince échoue.
  3. Ajoutez une propriété status à CustomObject. Demander à l'appelant de vérifier la propriété.

Je ne suis pas satisfait de l’une de ces trois options. Je n'aime pas les options 1 et 2 car je n'aime pas utiliser les exceptions pour le contrôle de flux. J'aime pas non plus renvoyer une valeur lorsque mon intention est d'indiquer qu'il n'y avait pas de valeur .

Néanmoins, je me penche vers l'option 3.

Existe-t-il une option que je n’envisage pas? Quelqu'un a-t-il des idées bien arrêtées concernant l'une ou l'autre de ces trois options?

Réponses à cette question, paraphrasées:

  1. Fournissez un contient méthode et obliger l'appelant à l'appeler avant d'appeler get (clé, ifModifiedSince) , jette exception si la clé n'existe pas, retourne null si l'objet n'a pas été modifié.
  2. Enveloppez la réponse et les données (le cas échéant) dans un objet composite.
  3. Utilisez une constante prédéfinie pour indiquer un état ( NON MODIFIE, KEY_DOES_NOT_EXIST ).
  4. L'appelant implémente l'interface pour être utilisé comme callbacks.
  5. Le design est nul.

Pourquoi je ne peux pas choisir la réponse 1

Je conviens que c’est la solution idéale, mais c’est une solution que j’ai déjà rejetée (à contrecoeur). Le problème de cette approche est que, dans la majorité des cas où ces classes seront utilisées, le système de stockage principal sera un système distant tiers, comme Amazon S3. Cela signifie qu'une méthode contient nécessiterait un aller-retour vers le système de stockage, suivi le plus souvent d'un autre aller-retour. Parce que cela coûterait à la fois du temps et de l'argent , ce n'est pas une option.

Sans cette limitation, ce serait la meilleure approche.

(Je réalise que je n'ai pas mentionné cet élément important dans la question, mais j'essayais d'être bref. Il était évident que c'était pertinent.)

Conclusion:

Après avoir lu toutes les réponses, je suis parvenu à la conclusion qu’un wrapper était la meilleure approche dans ce cas. Je vais essentiellement imiter HTTP, avec des métadonnées (en-têtes) comprenant un code de réponse et un corps de contenu (message).

Était-ce utile?

La solution

On dirait que vous voulez réellement renvoyer deux éléments: le code de réponse et l'objet trouvé. Vous pouvez envisager de créer un wrapper léger contenant les deux et de les renvoyer ensemble.

public class Pair<K,V>{
  public K first;
  public V second;
}

Ensuite, vous pouvez créer une nouvelle paire qui contient votre code de réponse et les données. En tant qu'effet secondaire à l'utilisation de génériques, vous pouvez ensuite réutiliser ce wrapper pour la paire dont vous avez réellement besoin.

De plus, si les données ne sont pas expirées, vous pouvez toujours les renvoyer, mais donnez-leur un code 303 pour leur faire savoir qu'elles sont inchangées. La série 4xx serait associée à null .

Autres conseils

Avec l'exigence donnée, vous ne pouvez pas faire cela.

Si vous avez conçu le contrat , ajoutez une condition et appelez l'appelant

.
exists(key): bool

L'implémentation du service ressemble à ceci:

if (exists(key)) {
    CustomObject o = get(key, ifModifiedSince);
    if (o == null) { 
      setResponseCode(302);
    } else {
      setResponseCode(200);
      push(o);
   }

} else {
      setResponseCode(400);
}

Le client reste inchangé et ne remarque jamais que vous avez validé d'avance.

Si vous n'avez pas conçu le contrat , il y a probablement une bonne raison à cela ou probablement uniquement une faute du concepteur (ou de l'architecte). Mais puisque vous ne pouvez pas le changer, vous n’avez pas à vous inquiéter non plus.

Ensuite, respectez les spécifications et procédez comme suit:

 CustomObject o = get(key, ifModifiedSince);

 if (o != null) {
     setResponseCode(200);
     push(o);
  } else {
     setResponseCode(404); // either not found or not modified.
  }

D'accord, vous n'envoyez pas 302 dans ce cas, mais c'est probablement ainsi que le système a été conçu.

Je veux dire, pour des raisons de sécurité, le serveur ne devrait pas renvoyer plus d'informations que celle-ci [la sonde est get (clé, date), elle ne renvoie que null ou objet]

Alors ne vous inquiétez pas pour ça. Parlez à votre responsable et informez-le de cette décision. Commentez le code avec cette décision aussi. Et si vous avez l’architecte en main, confirmez la raison de cette étrange restriction.

Il est fort probable qu'ils ne voient pas cela venir et qu'ils peuvent modifier le contrat après votre suggestion.

Parfois, si vous souhaitez procéder correctement, nous pouvons procéder de manière erronée et compromettre la sécurité de notre application.

Communiquez avec votre équipe.

Vous pouvez créer un CustomObject final spécial en tant que "marqueur". pour indiquer inchangé:

static public final CustomObject UNCHANGED=new CustomObject();

et recherchez une correspondance avec " == " au lieu de .equals ().

Cela pourrait également fonctionner de renvoyer null sur inchangé et de lancer une exception sur n'existe pas? Si je devais choisir l’un de vos 3, j’en choisirais un parce que cela semble être le cas le plus exceptionnel.

La recherche d’un objet qui n’existe pas me semble un cas exceptionnel. Couplé à une méthode qui permet à un appelant de déterminer s’il existe un objet, je pense qu’il serait correct de lancer l’exception quand ce n’est pas le cas.

public bool exists( String key ) { ... }

L'appelant peut faire:

if (exists(key)) {
   CustomObject modified = get(key,DateTime.Today.AddDays(-1));
   if (modified != null) { ... }
}

or

try {
    CustomObject modified = get(key,DateTime.Today.AddDays(-1));
}
catch (NotFoundException) { ... }

Le problème avec les exceptions est qu'elles sont censées signaler un "échec rapide". scénario (c'est-à-dire si elle n'est pas traitée, une exception arrêtera une application) en raison d'un comportement exceptionnel et anormal .

Je ne pense pas que "le scénario dans lequel la clé existe mais où l'objet n'a pas été modifié" est exceptionnel et certainement pas anormal.

Je ne voudrais donc pas utiliser exception, mais plutôt documenter l'action que l'appelant doit effectuer afin d'interpréter correctement le résultat (propriété ou objet spécial).

Quelle est l'exigence de cette signature de méthode?

Il semble que vous travailliez sur un projet en cours. Si les consommateurs de votre classe sont d’autres développeurs, pouvez-vous les convaincre que la signature de la méthode demandée est insuffisante? Peut-être n’ont-ils pas encore compris qu’il devrait exister deux modes de défaillance uniques (la clé n’existe pas et l’objet n’a pas été modifié).

J'en discuterais avec votre superviseur si c'est une option.

Je retournerais quand même null.

L'intention de la propriété est de renvoyer l'objet modifié après la date spécifiée. Si renvoyer null pour aucun objet est ok, renvoyer sûrement null pour un objet non modifié est également ok.

Personnellement, je renverrais null pour un objet non modifié et lirai une exception pour un objet non existant. Cela semble plus naturel.

Vous avez parfaitement raison de ne pas utiliser d'exceptions pour le contrôle de flux BTW. Par conséquent, si vous ne disposez que de ces 3 options, votre instinct est correct.

Vous pouvez suivre le modèle de bibliothèque .Net de l'objet personnalisé appelé CustomObject.Empty dans l'objet personnalisé readonly public, de type CustomObject (comme chaîne. Vide et Guid.Empty). Vous pouvez le renvoyer si l'objet n'est pas modifié (la fonction que le consommateur devra comparer).
Modifier: Je viens juste de remarquer que vous travaillez en Java, mais le principe est toujours valable

Ceci vous donne la possibilité de ce qui suit

  • Renvoie la valeur null si la clé n'existe pas.

  • Renvoyez CustomObject.Empty si la clé existe mais que l'objet n'a pas été modifié.

L’inconvénient est que le consommateur aurait besoin de connaître la différence entre une valeur de retour NULL et une valeur de retour CustomObject.Empty.

Peut-être que la propriété serait plus justement appelée CustomObject.NotModified , car Empty est réellement destinée aux types Value, car ils ne peuvent pas être null. De même, Non modifié permettrait au consommateur de mieux comprendre le sens du champ.

L’interface (prévue) concernant les exigences est sérieusement endommagée. Vous essayez de faire des choses sans lien avec une seule méthode. C'est la route de l'enfer logiciel.

Fournissez un rappel comme argument où la classe de rappel pourrait être soit induite par un événement, soit par un inducteur.

Vous demandez à l'interface de votre classe de définir les différentes erreurs pouvant survenir, en transmettant l'objet CustomObject en tant que paramètre de l'événement, si nécessaire.

public interface Callback {
  public void keyDoesNotExist();
  public void notModified(CustomObject c);
  public void isNewlyModified(CustomObject c);
  .
  .
  .
}

De cette manière, vous autorisez l'implémenteur de l'interface de rappel à définir l'action à exécuter lorsque l'événement se produit, et vous pouvez choisir via l'interface si ces conditions nécessitent le passage de l'objet récupéré. Enfin, cela réduit la complexité de la logique du retour. Votre méthode fait ça une fois. Les implémenteurs de l'API n'ont pas besoin de le faire, comme c'est le cas pour eux.

Si cela est acceptable, vous pouvez renvoyer un CustomObject amplifié (un wrapper), contenant les valeurs représentant l'objet et son état de modification, le cas échéant, etc.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top