2009-06-05 21 views
10

Questo è correlato alle convenzioni utilizzate in C#.Quale stile di ritorno dovrei usare?

Ho un metodo che ha due parametri (coordinate X e Y). Queste coordinate rappresentano la posizione in cui una "piastrella" può risiedere. Se una tessera risiede su queste coordinate, il metodo restituisce il suo numero. Se nessuna tessera risiede in queste coordinate, mi chiedo come dovrebbe comportarsi il metodo.

vedo tre opzioni:

  1. utilizzare le eccezioni. Potrei sollevare un'eccezione ogni volta che il Metodo non trova alcuna tessera. Tuttavia, poiché questa situazione non è rara, questa opzione è la peggiore.
  2. Esegui il vecchio modo C++ e restituisci -1 se non ci sono tessere.
  3. Rendi il numero di tile un parametro di riferimento e modifica il tipo di ritorno del metodo in booleano per mostrare se c'è una tessera o meno. Ma questo mi sembra un po 'complicato.

Quindi, cosa dovrei fare?

+1

Mi sembra, ho iniziato una guerra santa :) – undsoft

+4

+1 per la guerra santa! ;) – Adrien

+0

Ragazzi, grazie per le risposte. Non ho ancora conosciuto i tipi annullabili. Li avrò in mente più tardi. Ma per sapere di siprificare tutto userò l'opzione "return -1". – undsoft

risposta

20

ritorno -1.

Questa non è solo una convenzione C++, è anche comune in .NET Framework - ad es. metodi come String.IndexOf o proprietà come SelectedIndex per i controlli che rappresentano gli elenchi.

EDIT

Solo per elaborare, delle tre opzioni a tua domanda (Exception, restituire -1, fuori parametro), restituendo -1 è la strada da percorrere. Le eccezioni sono per situazioni eccezionali e le linee guida per la codifica di Microsoft consigliano di evitare i parametri laddove possibile.

A mio avviso restituire -1 (a condizione che sia sempre un valore non valido), restituire un valore Null int, o restituire un oggetto Tile sono tutte soluzioni accettabili, e si dovrebbe scegliere quale sia il più coerente con il resto del proprio app. Non riesco a immaginare ogni sviluppatore avrebbe avuto la minima difficoltà con uno dei seguenti:

int tileNumber = GetTile(x,y); 
if (tileNumber != -1) 
{ 
    ... use tileNumber ... 
} 


int? result = GetTile(x,y); 
if (result.HasValue) 
{ 
    int tileNumber = result.Value; 
    ... use tileNumber ... 
} 


Tile tile = GetTile(x,y); 
if (tile != null) 
{ 
    ... use tile ... 
} 

io non sono sicuro di aver capito il commento di Peter Ruderman sull'utilizzo di un int di essere "molto più efficiente di restituire un tipo nullable" . Avrei pensato che qualsiasi differenza sarebbe stata trascurabile.

+3

È comune solo perché i valori nulli non esistevano al momento in cui questi metodi sono stati creati o a causa della continuazione della comunanza (vale a dire che le vecchie abitudini sono difficili da eliminare). Nullables è una scelta molto più sicura, credo. – configurator

+1

È anche comune perché è molto più efficiente di restituire un tipo nullable. –

+5

"Nullables è una scelta molto più sicura". Dipende dal contesto. La cosa più importante è essere coerenti con le convenzioni che usi nel resto della tua app. – Joe

23

È possibile restituire null e controllare questo sul codice di chiamata.

Certo che avrebbe dovuto utilizzare un tipo nullable:

int? i = YourMethodHere(x, y); 
+1

Restituisce null è il modo corretto se in caso contrario viene restituito un oggetto. –

+0

Ciò impone anche il metodo per attivare automaticamente il valore restituito. Questo è abbastanza dispendioso per un problema così semplice. –

+1

"deve usare"? Sono assolutamente d'accordo. –

3

Se il metodo ha accesso agli oggetti di tile sottostanti, un'altra possibilità sarebbe quella di restituire l'oggetto tile stesso o null se non vi è alcun tile di questo tipo.

+0

Questo è quello che farei comunque, poiché è possibile accedere comunque al numero di tessera, se necessario. –

0

Se il metodo fa parte di una libreria di basso livello, il design .NET standard probabilmente impone di generare eccezioni dal metodo.

Ecco come funziona in genere il framework .NET. I tuoi chiamanti di livello superiore dovrebbero prendere le tue eccezioni.

Tuttavia, poiché sembra che tu stia facendo questo da un thread dell'interfaccia utente, che ha implicazioni sulle prestazioni dal momento che stai rispondendo agli eventi dell'interfaccia utente, faccio ciò che Jay Riggs ha già suggerito, restituisci null e assicurati che i chiamanti controllino un ritorno nullo valore.

6

Utilizzare un valore di ritorno nullable.

int? GetTile(int x, int y) { 
    if (...) 
     return SomeValue; 
    else 
     return null; 
} 

Questa è la soluzione più chiara.

17

Le eccezioni sono per casi eccezionali, in modo da utilizzare le eccezioni su un atteso situazione di errore noto e è "cattivo". È anche più probabile, ora, avere try-catch ovunque per gestire questo errore in modo specifico perché si prevede che si verifichi questa situazione di errore.

Per rendere il valore restituito un parametro è accettabile se l'unica condizione di errore (ad esempio -1) è confusibile con un valore reale. Se puoi avere un numero di tile negativo, questo è un modo migliore per andare.

Un valore int nullo è una possibile alternativa a un parametro di riferimento ma si creano oggetti con questo, quindi se un "errore" è di routine, è possibile che si stia facendo più lavoro in questo modo rispetto a un parametro di riferimento. Come ha sottolineato Roman in un commento altrove, si avranno problemi C# e VB con the nullable type introdotti troppo tardi per VB per fornire un buon zucchero sintattico come C#.

Se le tue tessere possono essere solo non negative, restituire -1 è un modo accettabile e tradizionale per indicare un errore. Sarebbe anche il meno costoso in termini di prestazioni e memoria.


Un'altra cosa da considerare è l'auto-documentazione. L'utilizzo di -1 e un'eccezione sono convenzionali: dovresti scrivere la documentazione per assicurarti che lo sviluppatore ne sia a conoscenza. Utilizzando un ritorno int? o un parametro di riferimento sarebbe meglio auto-descrivere se stesso e non sarebbe richiedere la documentazione per uno sviluppatore di sapere come gestire la situazione di errore. Certo :) dovresti sempre scrivere la documentazione, proprio come dovresti usare il filo interdentale ogni giorno.

+1

Non sono d'accordo con la tua prima affermazione. Le eccezioni riguardano situazioni in cui il metodo non può fare ciò che promette di fare. Il fatto che questo dovrebbe essere un caso raro è secondario. Ovviamente, le prestazioni dovrebbero essere prese in considerazione. –

+2

+1 Di gran lunga la spiegazione migliore e più chiara. –

+2

Pyran, stai dividendo i capelli. undsoft * sa * che una tessera non trovata sarà comune ed è * supposto * gestire le tessere mancanti con grazia. Se, ad esempio, le coordinate fossero fuori limite, sarebbe eccezionale. Tuttavia, se si trattava di un metodo vicino agli input degli utenti e gli errori di outbound dovevano essere spesso, allora, di nuovo, un'eccezione sarebbe la strada sbagliata. –

2

Vorrei andare con l'opzione 2. Hai ragione, lanciare un'eccezione in un caso così comune può essere negativo per le prestazioni, e usare un parametro out e restituire un valore vero o falso è utile ma vistoso da leggere.

Inoltre, pensare al metodo string.IndexOf(). Se non viene trovato nulla, restituisce -1. Seguirò quell'esempio.

2

È possibile restituire -1, poiché si tratta di un approccio C# piuttosto comune. Tuttavia, potrebbe essere meglio restituire effettivamente il riquadro su cui è stato fatto clic e, nel caso in cui non sia stato fatto clic su alcun riquadro, restituire un riferimento a un'istanza NullTile singleton. Il vantaggio di farlo in questo modo è dare un significato concreto a ciascun valore restituito, piuttosto che essere semplicemente un numero che non ha un significato intrinseco oltre il suo valore numerico. Un tipo 'NullTile' è molto specifico sul suo significato, lasciando poco a dubbi per gli altri lettori del tuo codice.

0

L'ho suddiviso in due metodi. Avere qualcosa come CheckTileExists(x,y) e GetTile(x,y). Il primo restituisce un valore booleano che indica se c'è o meno una tessera alle coordinate date. Il secondo metodo è essenzialmente quello di cui si sta parlando nel post originale, tranne che dovrebbe generare un'eccezione quando vengono fornite le coordinate non valide (poiché indica che il chiamante non ha prima chiamato CheckTileExists(), quindi è legittimamente una situazione eccezionale.Per motivi di velocità, probabilmente vorrai questi due metodi per condividere una cache, in modo che nel caso in cui vengano chiamati uno dopo l'altro, il sovraccarico sulla funzione GetTile() sarebbe trascurabile. Non so se hai già un oggetto adatto su cui mettere questi metodi o se forse dovresti renderli due metodi su una nuova classe. IMHO, la penalizzazione delle prestazioni di questo approccio è trascurabile e l'aumento della chiarezza del codice lo supera di gran lunga.

+0

Overkill. Inoltre, cosa succede se si tratta di un'app multithread e il riquadro può apparire/scomparire tra le chiamate a CheckTileExists e GetTile? – Joe

+1

@Joe: se ciò può accadere, allora quale è il valore restituito da GetTile() in primo luogo, dal momento che potrebbe scomparire da sotto di te? – rmeador

+0

"se ciò può accadere, allora a che serve il valore restituito" - hai ragione, ovviamente, è stato un commento stupido (continuo a pensare che sia comunque eccessivo). – Joe

0

È possibile che sia stato creato (o che si possa creare) un oggetto Tile a cui si fa riferimento alle coordinate? Se è così, si può restituire un riferimento a tale piastrelle o null se non ci sono mattonelle nelle coordinate indicate:

public Tile GetTile(int x, int y) { 
    if (!TileExists(x, y)) 
     return null; 
    // ... tile lookup here... 
} 
2

le opzioni migliori sono per restituire un valore booleano come bene o restituire null.

ad es.

bool TryGetTile(int x, int y, out int tile); 

o,

int? GetTile(int x, int y); 

Ci sono diversi motivi per preferire il modello "TryGetValue". Ad esempio, restituisce un valore booleano, quindi il codice cliente è incredibilmente semplice, ad esempio: if (TryGetValue (out someVal)) {/ * un codice * /}. Confrontalo con il codice client che richiede confronti a valori sentinella hard-coded (a -1, 0, null, catturando un particolare insieme di eccezioni, ecc.) "I numeri magici" si trasformano rapidamente in quei progetti e il factoring del tight-coupling diventa un lavoretto.

Quando sono previsti valori sentinella, null o eccezioni, è assolutamente fondamentale controllare la documentazione su quale meccanismo è utilizzato. Se la documentazione non esiste o non è accessibile, uno scenario comune, allora devi dedurre in base ad altre prove, se fai la scelta sbagliata ti stai semplicemente impostando per un'eccezione di riferimento null o altri difetti difettosi. Considerando che, il pattern TryGetValue() è piuttosto vicino all'autodocumentazione dal nome e dalla firma del metodo da solo.

+0

Se devi restituire false ("nessuna tessera"), quale valore avrà il parametro tile? Certo, può essere qualsiasi valore, ma per il tipo int è strano. Sarebbe ok se fosse un oggetto renderlo nullo e restituire falso. Se è un int, e qualcuno si dimentica di controllare il valore di ritorno di una funzione, userà il valore sbagliato (diciamo "0" o "-1"). Non è un buon modo di programmare. – Roma

+0

@Roman, credo che il tuo ragionamento non sia corretto. TryGetTile() può fare tutto ciò che vuole sul valore di uscita se fallisce, molto probabilmente non modificherà il parametro di output. Dire che questo è più pericoloso di altri metodi è piuttosto ridicolo. Non c'è certamente alcun pericolo maggiore qui che se GetTile restituisce un valore nullo e si dimentica di verificare la presenza di null prima di eseguire operazioni sul valore restituito. In effetti, la denominazione di TryGetTile stesso rende enormemente chiaro esattamente come dovrebbe essere usato e rende tale errore molto, molto più facile da individuare nel codice. – Wedge

1

Ho la mia opinione sulla domanda che hai chiesto, ma è stata dichiarata sopra e ho votato di conseguenza.

Per quanto riguarda la domanda che non hai chiesto, o almeno come estensione a tutte le risposte di cui sopra: sarei sicuro di mantenere la soluzione per situazioni simili coerenti in tutta l'applicazione. In altre parole, qualunque risposta tu stabilisca, mantienila la stessa all'interno dell'app.