C# Language
Manejo de excepciones
Buscar..
Manejo básico de excepciones
try
{
/* code that could throw an exception */
}
catch (Exception ex)
{
/* handle the exception */
}
Tenga en cuenta que el manejo de todas las excepciones con el mismo código a menudo no es el mejor enfoque.
Esto se usa comúnmente cuando falla alguna rutina interna de manejo de excepciones, como último recurso.
Manejo de tipos de excepción específicos
try
{
/* code to open a file */
}
catch (System.IO.FileNotFoundException)
{
/* code to handle the file being not found */
}
catch (System.IO.UnauthorizedAccessException)
{
/* code to handle not being allowed access to the file */
}
catch (System.IO.IOException)
{
/* code to handle IOException or it's descendant other than the previous two */
}
catch (System.Exception)
{
/* code to handle other errors */
}
Tenga cuidado de que las excepciones se evalúen en orden y se aplique la herencia. Así que necesitas comenzar con los más específicos y terminar con su ancestro. En cualquier punto dado, solo se ejecutará un bloque catch.
Usando el objeto de excepción
Se le permite crear y lanzar excepciones en su propio código. La creación de una excepción se realiza de la misma manera que cualquier otro objeto C #.
Exception ex = new Exception();
// constructor with an overload that takes a message string
Exception ex = new Exception("Error message");
A continuación, puede utilizar la palabra clave throw
para provocar la excepción:
try
{
throw new Exception("Error");
}
catch (Exception ex)
{
Console.Write(ex.Message); // Logs 'Error' to the output window
}
Nota: si está lanzando una nueva excepción dentro de un bloque catch, asegúrese de que la excepción original se pase como "excepción interna", por ejemplo
void DoSomething()
{
int b=1; int c=5;
try
{
var a = 1;
b = a - 1;
c = a / b;
a = a / c;
}
catch (DivideByZeroException dEx) when (b==0)
{
// we're throwing the same kind of exception
throw new DivideByZeroException("Cannot divide by b because it is zero", dEx);
}
catch (DivideByZeroException dEx) when (c==0)
{
// we're throwing the same kind of exception
throw new DivideByZeroException("Cannot divide by c because it is zero", dEx);
}
}
void Main()
{
try
{
DoSomething();
}
catch (Exception ex)
{
// Logs full error information (incl. inner exception)
Console.Write(ex.ToString());
}
}
En este caso, se supone que la excepción no se puede manejar, pero se agrega información útil al mensaje (y se puede acceder a la excepción original a través de ex.InnerException
mediante un bloque de excepción externo).
Se mostrará algo como:
System.DivideByZeroException: no se puede dividir entre b porque es cero ---> System.DivideByZeroException: se intentó dividir entre cero.
en UserQuery.g__DoSomething0_0 () en C: [...] \ LINQPadQuery.cs: línea 36
--- Fin del rastro de la pila de excepción interna ---
en UserQuery.g__DoSomething0_0 () en C: [...] \ LINQPadQuery.cs: línea 42
en UserQuery.Main () en C: [...] \ LINQPadQuery.cs: línea 55
Si está probando este ejemplo en LinqPad, notará que los números de línea no son muy significativos (no siempre lo ayudan). Pero pasar un texto de error útil como se sugiere anteriormente a menudo reduce significativamente el tiempo para rastrear la ubicación del error, que en este ejemplo es claramente la línea
c = a / b;
en la función DoSomething()
.
Finalmente bloque
try
{
/* code that could throw an exception */
}
catch (Exception)
{
/* handle the exception */
}
finally
{
/* Code that will be executed, regardless if an exception was thrown / caught or not */
}
El bloque try / catch / finally
puede ser muy útil al leer archivos.
Por ejemplo:
FileStream f = null;
try
{
f = File.OpenRead("file.txt");
/* process the file here */
}
finally
{
f?.Close(); // f may be null, so use the null conditional operator.
}
Un bloque try debe ir seguido de un catch
o un bloque finally
. Sin embargo, como no hay bloque catch, la ejecución causará la terminación. Antes de la terminación, las declaraciones dentro del bloque finally serán ejecutadas.
En la lectura de archivos podríamos haber usado un bloque de using
como FileStream
(lo que devuelve OpenRead
) implementa como IDisposable
.
Incluso si hay una declaración de return
en el bloque try
, el bloque finally
generalmente se ejecutará; Hay algunos casos en los que no:
- Cuando se produce un StackOverflow .
-
Environment.FailFast
- El proceso de solicitud es eliminado, generalmente por una fuente externa.
Implementando IErrorHandler para los servicios WCF
La implementación de IErrorHandler para los servicios WCF es una excelente manera de centralizar el manejo de errores y el registro. La implementación que se muestra aquí debería detectar cualquier excepción no controlada que se haya generado como resultado de una llamada a uno de sus servicios WCF. También se muestra en este ejemplo cómo devolver un objeto personalizado y cómo devolver JSON en lugar del XML predeterminado.
Implementar IErrorHandler:
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace BehaviorsAndInspectors
{
public class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception ex)
{
// Log exceptions here
return true;
} // end
public void ProvideFault(Exception ex, MessageVersion version, ref Message fault)
{
// Get the outgoing response portion of the current context
var response = WebOperationContext.Current.OutgoingResponse;
// Set the default http status code
response.StatusCode = HttpStatusCode.InternalServerError;
// Add ContentType header that specifies we are using JSON
response.ContentType = new MediaTypeHeaderValue("application/json").ToString();
// Create the fault message that is returned (note the ref parameter) with BaseDataResponseContract
fault = Message.CreateMessage(
version,
string.Empty,
new CustomReturnType { ErrorMessage = "An unhandled exception occurred!" },
new DataContractJsonSerializer(typeof(BaseDataResponseContract), new List<Type> { typeof(BaseDataResponseContract) }));
if (ex.GetType() == typeof(VariousExceptionTypes))
{
// You might want to catch different types of exceptions here and process them differently
}
// Tell WCF to use JSON encoding rather than default XML
var webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty);
} // end
} // end class
} // end namespace
En este ejemplo adjuntamos el controlador al comportamiento del servicio. También puede adjuntar esto a IEndpointBehavior, IContractBehavior o IOperationBehavior de una manera similar.
Adjuntar a los comportamientos de servicio:
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace BehaviorsAndInspectors
{
public class ErrorHandlerExtension : BehaviorExtensionElement, IServiceBehavior
{
public override Type BehaviorType
{
get { return GetType(); }
}
protected override object CreateBehavior()
{
return this;
}
private IErrorHandler GetInstance()
{
return new ErrorHandler();
}
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } // end
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
var errorHandlerInstance = GetInstance();
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(errorHandlerInstance);
}
}
void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } // end
} // end class
} // end namespace
Configuraciones en Web.config:
...
<system.serviceModel>
<services>
<service name="WebServices.MyService">
<endpoint binding="webHttpBinding" contract="WebServices.IMyService" />
</service>
</services>
<extensions>
<behaviorExtensions>
<!-- This extension if for the WCF Error Handling-->
<add name="ErrorHandlerBehavior" type="WebServices.BehaviorsAndInspectors.ErrorHandlerExtensionBehavior, WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<ErrorHandlerBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
....
</system.serviceModel>
...
Aquí hay algunos enlaces que pueden ser útiles sobre este tema:
https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.100).aspx
Otros ejemplos:
IErrorHandler no parece estar manejando mis errores en WCF ... ¿alguna idea?
¿Cómo configura el encabezado Content-Type para una solicitud HttpClient?
Creación de excepciones personalizadas
Se le permite implementar excepciones personalizadas que pueden lanzarse como cualquier otra excepción. Esto tiene sentido cuando quiere hacer que sus excepciones sean distinguibles de otros errores durante el tiempo de ejecución.
En este ejemplo, crearemos una excepción personalizada para el manejo claro de los problemas que puede tener la aplicación al analizar una entrada compleja.
Creación de una clase de excepción personalizada
Para crear una excepción personalizada, cree una subclase de Exception
:
public class ParserException : Exception
{
public ParserException() :
base("The parsing went wrong and we have no additional information.") { }
}
La excepción personalizada se vuelve muy útil cuando desea proporcionar información adicional al receptor:
public class ParserException : Exception
{
public ParserException(string fileName, int lineNumber) :
base($"Parser error in {fileName}:{lineNumber}")
{
FileName = fileName;
LineNumber = lineNumber;
}
public string FileName {get; private set;}
public int LineNumber {get; private set;}
}
Ahora, cuando catch(ParserException x)
tendrá una semántica adicional para afinar el manejo de excepciones.
Las clases personalizadas pueden implementar las siguientes funciones para admitir escenarios adicionales.
relanzamiento
Durante el proceso de análisis, la excepción original sigue siendo de interés. En este ejemplo, es una FormatException
porque el código intenta analizar un fragmento de cadena, que se espera que sea un número. En este caso, la excepción personalizada debe admitir la inclusión de la ' InnerException ':
//new constructor:
ParserException(string msg, Exception inner) : base(msg, inner) {
}
publicación por entregas
En algunos casos, es posible que sus excepciones tengan que cruzar los límites de dominio de aplicación. Este es el caso si su analizador se ejecuta en su propio dominio de aplicación para admitir la recarga en caliente de las nuevas configuraciones del analizador. En Visual Studio, puede usar la plantilla de Exception
para generar código como este.
[Serializable]
public class ParserException : Exception
{
// Constructor without arguments allows throwing your exception without
// providing any information, including error message. Should be included
// if your exception is meaningful without any additional details. Should
// set message by calling base constructor (default message is not helpful).
public ParserException()
: base("Parser failure.")
{}
// Constructor with message argument allows overriding default error message.
// Should be included if users can provide more helpful messages than
// generic automatically generated messages.
public ParserException(string message)
: base(message)
{}
// Constructor for serialization support. If your exception contains custom
// properties, read their values here.
protected ParserException(SerializationInfo info, StreamingContext context)
: base(info, context)
{}
}
Usando la excepción ParserException
try
{
Process.StartRun(fileName)
}
catch (ParserException ex)
{
Console.WriteLine($"{ex.Message} in ${ex.FileName}:${ex.LineNumber}");
}
catch (PostProcessException x)
{
...
}
También puede usar excepciones personalizadas para atrapar y envolver excepciones. De esta manera, muchos errores diferentes se pueden convertir en un solo tipo de error que es más útil para la aplicación:
try
{
int foo = int.Parse(token);
}
catch (FormatException ex)
{
//Assuming you added this constructor
throw new ParserException(
$"Failed to read {token} as number.",
FileName,
LineNumber,
ex);
}
Cuando maneje las excepciones al crear sus propias excepciones personalizadas, generalmente debe incluir una referencia a la excepción original en la propiedad InnerException
, como se muestra arriba.
Preocupaciones de seguridad
Si exponer el motivo de la excepción podría comprometer la seguridad al permitir que los usuarios vean el funcionamiento interno de su aplicación, puede ser una mala idea envolver la excepción interna. Esto podría aplicarse si está creando una biblioteca de clases que será utilizada por otros.
Aquí es cómo podría generar una excepción personalizada sin envolver la excepción interna:
try
{
// ...
}
catch (SomeStandardException ex)
{
// ...
throw new MyCustomException(someMessage);
}
Conclusión
Al generar una excepción personalizada (ya sea con una envoltura o con una nueva excepción no envuelta), debe presentar una excepción que sea significativa para la persona que llama. Por ejemplo, un usuario de una biblioteca de clases puede no saber mucho sobre cómo esa biblioteca hace su trabajo interno. Las excepciones que son lanzadas por las dependencias de la biblioteca de clases no son significativas. Más bien, el usuario desea una excepción que sea relevante para la forma en que la biblioteca de clases usa esas dependencias de manera errónea.
try
{
// ...
}
catch (IOException ex)
{
// ...
throw new StorageServiceException(@"The Storage Service encountered a problem saving
your data. Please consult the inner exception for technical details.
If you are not able to resolve the problem, please call 555-555-1234 for technical
assistance.", ex);
}
Excepción Anti-patrones
Tragar excepciones
Siempre se debe volver a lanzar la excepción de la siguiente manera:
try
{
...
}
catch (Exception ex)
{
...
throw;
}
Volver a lanzar una excepción como la que se muestra a continuación ocultará la excepción original y perderá el seguimiento de la pila original. ¡Uno nunca debe hacer esto! La traza de la pila antes de la captura y el retropropósito se perderá.
try
{
...
}
catch (Exception ex)
{
...
throw ex;
}
Manejo de excepciones de béisbol
Uno no debe usar excepciones como sustituto de las construcciones normales de control de flujo como las sentencias if-then y while. Este anti-patrón a veces se llama Baseball Exception Handling .
Aquí hay un ejemplo del anti-patrón:
try
{
while (AccountManager.HasMoreAccounts())
{
account = AccountManager.GetNextAccount();
if (account.Name == userName)
{
//We found it
throw new AccountFoundException(account);
}
}
}
catch (AccountFoundException found)
{
Console.Write("Here are your account details: " + found.Account.Details.ToString());
}
Aquí hay una mejor manera de hacerlo:
Account found = null;
while (AccountManager.HasMoreAccounts() && (found==null))
{
account = AccountManager.GetNextAccount();
if (account.Name == userName)
{
//We found it
found = account;
}
}
Console.Write("Here are your account details: " + found.Details.ToString());
captura (Excepción)
Casi no hay razones (¡algunos dicen que no!) Para detectar el tipo de excepción genérico en su código. Debería capturar solo los tipos de excepción que espera que ocurran, porque de lo contrario ocultará los errores en su código.
try
{
var f = File.Open(myfile);
// do something
}
catch (Exception x)
{
// Assume file not found
Console.Write("Could not open file");
// but maybe the error was a NullReferenceException because of a bug in the file handling code?
}
Mejor hacer
try
{
var f = File.Open(myfile);
// do something which should normally not throw exceptions
}
catch (IOException)
{
Console.Write("File not found");
}
// Unfortunatelly, this one does not derive from the above, so declare separatelly
catch (UnauthorizedAccessException)
{
Console.Write("Insufficient rights");
}
Si ocurre alguna otra excepción, dejamos que la aplicación se bloquee a propósito, por lo que directamente entra en el depurador y podemos solucionar el problema. No debemos enviar un programa en el que ocurran otras excepciones que no sean estas, por lo que no es un problema tener un bloqueo.
El siguiente es un mal ejemplo, también, porque utiliza excepciones para evitar un error de programación. Eso no es para lo que están diseñados.
public void DoSomething(String s)
{
if (s == null)
throw new ArgumentNullException(nameof(s));
// Implementation goes here
}
try
{
DoSomething(myString);
}
catch(ArgumentNullException x)
{
// if this happens, we have a programming error and we should check
// why myString was null in the first place.
}
Excepciones agregadas / excepciones múltiples de un método
Quien dice que no puedes lanzar múltiples excepciones en un método. Si no estás acostumbrado a jugar con AggregateExceptions, puedes tener la tentación de crear tu propia estructura de datos para representar muchas cosas que van mal. Por supuesto, hay otra estructura de datos que no es una excepción sería más ideal, como los resultados de una validación. Incluso si juegas con AggregateExceptions puedes estar en el lado receptor y siempre manejarlas sin darte cuenta de que pueden serte de utilidad.
Es bastante plausible tener un método ejecutado y aunque será un error en su conjunto, querrá resaltar varias cosas que salieron mal en las excepciones que se lanzan. Como ejemplo, este comportamiento se puede ver con cómo funcionan los métodos paralelos si una tarea se divide en varios subprocesos y cualquier número de ellos podría generar excepciones y esto debe informarse. Aquí hay un ejemplo tonto de cómo podría beneficiarse de esto:
public void Run()
{
try
{
this.SillyMethod(1, 2);
}
catch (AggregateException ex)
{
Console.WriteLine(ex.Message);
foreach (Exception innerException in ex.InnerExceptions)
{
Console.WriteLine(innerException.Message);
}
}
}
private void SillyMethod(int input1, int input2)
{
var exceptions = new List<Exception>();
if (input1 == 1)
{
exceptions.Add(new ArgumentException("I do not like ones"));
}
if (input2 == 2)
{
exceptions.Add(new ArgumentException("I do not like twos"));
}
if (exceptions.Any())
{
throw new AggregateException("Funny stuff happended during execution", exceptions);
}
}
Anidamiento de excepciones y prueba de captura de bloques.
Uno es capaz de anidar un bloque de catch
excepción / try
dentro del otro.
De esta manera, se pueden administrar pequeños bloques de código que pueden funcionar sin interrumpir todo su mecanismo.
try
{
//some code here
try
{
//some thing which throws an exception. For Eg : divide by 0
}
catch (DivideByZeroException dzEx)
{
//handle here only this exception
//throw from here will be passed on to the parent catch block
}
finally
{
//any thing to do after it is done.
}
//resume from here & proceed as normal;
}
catch(Exception e)
{
//handle here
}
Nota: Evite tragar excepciones al lanzar al bloque de captura principal
Mejores prácticas
Hoja de trucos
HACER | NO HACER |
---|---|
Flujo de control con declaraciones de control | Control de flujo con excepciones. |
Mantenga un registro de la excepción ignorada (absorbida) mediante el registro | Ignorar la excepción |
Repita la excepción usando el throw | Volver a lanzar la excepción: throw new ArgumentNullException() o throw ex |
Lanzar excepciones del sistema predefinidas | Lanzar excepciones personalizadas similares a las excepciones del sistema predefinidas |
Lanzar excepción personalizada / predefinida si es crucial para la lógica de la aplicación | Lanzar excepciones personalizadas / predefinidas para indicar una advertencia en el flujo |
Captura las excepciones que quieras manejar | Atrapar todas las excepciones |
NO maneje la lógica de negocios con excepciones.
El control de flujo NO debe hacerse por excepciones. Utilice declaraciones condicionales en su lugar. Si se puede hacer un control con la instrucción if-else
claramente, no use excepciones porque reduce la legibilidad y el rendimiento.
Considere el siguiente fragmento de código por el Sr. Bad Practices:
// This is a snippet example for DO NOT
object myObject;
void DoingSomethingWithMyObject()
{
Console.WriteLine(myObject.ToString());
}
Cuando la ejecución llega a Console.WriteLine(myObject.ToString());
La aplicación lanzará una NullReferenceException. El Sr. Bad Practices se dio cuenta de que myObject
es nulo y editó su fragmento para capturar y manejar NullReferenceException
:
// This is a snippet example for DO NOT
object myObject;
void DoingSomethingWithMyObject()
{
try
{
Console.WriteLine(myObject.ToString());
}
catch(NullReferenceException ex)
{
// Hmmm, if I create a new instance of object and assign it to myObject:
myObject = new object();
// Nice, now I can continue to work with myObject
DoSomethingElseWithMyObject();
}
}
Dado que el fragmento de código anterior solo cubre la lógica de excepción, ¿qué debo hacer si myObject
no es nulo en este momento? ¿Dónde debería cubrir esta parte de la lógica? Justo después de Console.WriteLine(myObject.ToString());
? ¿Qué tal después del try...catch
block?
¿Qué tal el Sr. Mejores Prácticas? ¿Cómo manejaría esto?
// This is a snippet example for DO
object myObject;
void DoingSomethingWithMyObject()
{
if(myObject == null)
myObject = new object();
// When execution reaches this point, we are sure that myObject is not null
DoSomethingElseWithMyObject();
}
El Sr. Best Practices logró la misma lógica con menos código y una lógica clara y comprensible.
NO vuelva a lanzar Excepciones
Volver a lanzar excepciones es costoso. Tiene un impacto negativo en el rendimiento. Para el código que normalmente falla, puede usar patrones de diseño para minimizar los problemas de rendimiento. Este tema describe dos patrones de diseño que son útiles cuando las excepciones pueden afectar significativamente el rendimiento.
NO absorba excepciones sin registro
try
{
//Some code that might throw an exception
}
catch(Exception ex)
{
//empty catch block, bad practice
}
Nunca trague excepciones. Ignorar las excepciones ahorrará ese momento, pero creará un caos para el mantenimiento posterior. Al registrar excepciones, siempre debe registrar la instancia de excepción para que se registre el seguimiento completo de la pila y no solo el mensaje de excepción.
try
{
//Some code that might throw an exception
}
catch(NullException ex)
{
LogManager.Log(ex.ToString());
}
No atrapes excepciones que no puedas manejar
Muchos recursos, como este , le recomiendan encarecidamente que considere por qué está detectando una excepción en el lugar donde la está detectando. Solo debe capturar una excepción si puede manejarla en esa ubicación. Si puede hacer algo allí para ayudar a mitigar el problema, como probar un algoritmo alternativo, conectarse a una base de datos de respaldo, probar otro nombre de archivo, esperar 30 segundos e intentarlo nuevamente o notificar a un administrador, puede detectar el error y hacerlo. Si no hay nada que pueda hacer de manera plausible y razonable, simplemente "déjelo" y deje que la excepción se maneje en un nivel superior. Si la excepción es lo suficientemente catastrófica y no existe otra opción razonable que no sea que todo el programa se bloquee debido a la gravedad del problema, entonces deje que se bloquee.
try
{
//Try to save the data to the main database.
}
catch(SqlException ex)
{
//Try to save the data to the alternative database.
}
//If anything other than a SqlException is thrown, there is nothing we can do here. Let the exception bubble up to a level where it can be handled.
Excepción no controlada y de rosca
AppDomain.UnhandledException Este evento proporciona una notificación de excepciones no detectadas. Permite que la aplicación registre información sobre la excepción antes de que el controlador predeterminado del sistema notifique la excepción al usuario y finalice la aplicación. Si hay suficiente información sobre el estado de la aplicación, otra se pueden emprender acciones, como guardar los datos del programa para su posterior recuperación. Se recomienda precaución, ya que los datos del programa pueden corromperse cuando no se manejan las excepciones.
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);
}
Application.ThreadException Este evento permite que su aplicación de Windows Forms maneje las excepciones no controladas que ocurren en los hilos de Windows Forms. Adjunte sus controladores de eventos al evento ThreadException para lidiar con estas excepciones, lo que dejará su aplicación en un estado desconocido. Donde sea posible, las excepciones deben ser manejadas por un bloque estructurado de manejo de excepciones.
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);
Application.ThreadException += new ThreadExceptionEventHandler(ThreadException);
}
Y finalmente el manejo de excepciones.
static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
// your code
}
static void ThreadException(object sender, ThreadExceptionEventArgs e)
{
Exception ex = e.Exception;
// your code
}
Lanzar una excepción
Su código puede, y con frecuencia debería, lanzar una excepción cuando ha ocurrido algo inusual.
public void WalkInto(Destination destination)
{
if (destination.Name == "Mordor")
{
throw new InvalidOperationException("One does not simply walk into Mordor.");
}
// ... Implement your normal walking code here.
}