Exception Handling
I would not worry about how "heavy" exceptions have such code. I will explain why later.
Exceptions for exceptional situations. The presence of an exception handler means that these properties will never be zero.
When to write protection conditions
Is null common to these properties?
If so, this is not an exceptional situation. You must write the appropriate null tests and change the exception handler code to the null condition code.
Is it unusual for these properties to be null , i.e. can you reasonably expect them to never be null ?
If so, you could just not write null checks, but just let the code drop out. However, you will not get much context as to which property the exception selected.
You can also perform a null check and throw an exception that is more specific to the context.
When to write an exception handler
If these properties are unusually null , then this is an exception. But this does not mean that you must have a handler.
Do you have an easy way to check for an exception before this happens?
If so, then you should test this before allowing the use of base code for exception. Since you just need to check for null , I would say this is pretty easy.
Do you have reasonable logic to handle this case at this level?
If you have a reasonable way to handle the exception at this level and still ensure that your method runs correctly, then go ahead and add the handler code. If you rely on a mechanism, for example, returning null , then be sure that from the point of view of the consumer it makes sense that they will not always get the result. For instance. name the FindInterestingString method, not GetInterestingString .
Unless you have a reasonable way to handle the situation, do not place an exception handler at this level. Let your exception bubble up and handle it at a higher place in the code.
If you donโt have a reasonable way to handle any exception, just let the program crash. It is always better than swallowing an exception and continuing. This hides errors.
Exceptions to these rules
Sometimes you cannot easily check a condition without exception.
External dependencies, such as a file system, will change under your program. Even if you perform a preliminary test, and even if a preliminary test passes, an exception can be thrown as soon as you try to use this object. In such cases, you can do nothing about it and must rely on exception handling.
Complex validation, such as email addresses and URIs, may require the use of a construct that throws an exception. Again, this is not so. You should always look for the most suitable error handling method that suits your intentions. Just bend to use exception handling when you need to.
Performance
Performance is very unlikely to be a problem in error detection code.
Performance issues in high-performance code (when you write the framework), in the bottlenecks of your application and in algorithms that are known to work intensively in CPU / memory. You should learn when to worry about performance, but this should always be a secondary concern for the readability, maintainability, and correctness of your code.
You will find that it is impossible to accurately predict what will become a performance issue in your entire application. The only way to get an accurate picture is to run your code with realistic scripts in real conditions and profile it. Until you get to the development of the application, you should not worry about performance, unless you know that this will be a problem.
Using exceptions is not priced as high as many people would have believed. In .Net, they are designed to work very well when an exception is not thrown. This is because exceptions are for exceptions.
Code Samples
There were several additional issues with the code samples that you provided. I hope I can point out some of them before you trap them. If not, I hope you can look back at them for guidance in case of problems.
Writing Exception Handlers
Code written for your exception handler is generally not acceptable. Here are some guidelines for writing better exception handler code:
Poorly:
try { } catch
This is a bad form and should never be used. There is absolutely no way to properly handle all types of exceptions. The most commonly used example is an OutOfMemoryException .
Perhaps acceptable:
try { } catch(Exception e) { logger.Log(e.ToString());
If you catch an exception and register it or display it, the exception may be thrown. This is normal if you actively monitor / report these exceptions and have the ability to ensure that these exceptions are diagnosed.
So:
try { } catch(Exception e) { logger.Log(e.ToString()); // Make sure your logger never throws... throw; // Note: *not* `throw e;` } // Or: try { } catch { // Todo: Do something here, but be very careful... throw; }
You can do whatever you want in the exception handler if you are very careful not to create new exceptions, and if you re-select the exception . This ensures that an error is noticed. If you re- throw; exception, be sure to use throw; , not throw e; otherwise, your original stack trace will be destroyed.
Good:
try { } catch(NullReferenceException e) {
This is safe because you only catch certain types of exceptions that are known to be generated by code in the try block. It is easy to understand the intent of the code and it is easy to verify the code. It's easy to see if the exception handler code is good or not.
Avoid code duplication
Never move on to properties when they can be avoided. Instead of writing code that accesses your properties as follows:
rootObject ... rootObject.FirstProperty ... rootObject.FirstProperty.SecondProperty ... rootObject.FirstProperty.SecondProperty.InterestingString ...
... call getters only once:
var firstProperty = rootObject.FirstProperty; var secondProperty = firstProperty.SecondProperty; var interestingString = secondProperty.InterestingString;
An example of your code will look like this:
if (rootObject != null) { var firstProperty = rootObject.FirstProperty; if (firstProperty != null) { var secondProperty = firstProperty.SecondProperty; if (secondProperty != null) { var interestingString = secondProperty.InterestingString; if (!string.IsNullOrEmpty(interestingString)) { result = interestingString; } } } }
One reason for this is that getters can have complex logic and repeatedly call it several times, which can affect performance.
Another reason is that you avoid repetition . Code is always read when it does not have many repetitions.
When you repeat, it is also affected by maintainability. If you change the name of one of these properties, you will have to change each line of code in which it occurs, making it difficult to reason about the impact of the change.
Avoid deepening the dependency hierarchy too deeply.
You should avoid accessing the chain within the same method. I.e:.
rootObject.FirstProperty.SecondProperty.InterestingString
Even if you split it so as not to repeat yourself (as I said above), you probably didnโt correctly consider your code. Your code is still closely related to the hierarchy of this data structure. Every time you change this hierarchy, any code that crosses this hierarchy will need to be changed. If this is your code, then you are in poor condition.
To avoid this, separate the code that knows about each level from the levels below it.
The code that processes the root object must invoke code that processes the objects directly below the root. The code that processes FirstProperty should only know properties at the SecondProperty level (under FirstProperty ). The only code that should know anything about InterestingString is the handler code for the type of object returned by SecondProperty .
An easy way to do this is to break the workaround code and move it into the objects themselves.
Cm:
Example code that separates logic:
public class SomeClassUsingRoot { public string FindInterestingString() { return root != null ? root.FindInterestingString() : null; } private RootSomething root; } public class RootSomething { public string FindInterestingString() { return FirstProperty != null ? FirstProperty.FindInterestingString() : null; } public SomethingTopLevel FirstProperty { get; set; } } public class SomethingTopLevel { public string FindInterestingString() { return SecondProperty != null ? SecondProperty.InterestingString : null; } public SomethingLowerLevel SecondProperty { get; set; } } public class SomethingLowerLevel { public string InterestingString { get; set; } }
This is not the only way to solve the problem. The key is to split the logic that processes each level into separate methods or (even better) separate objects. Thus, you reduce the influence of the hierarchy.