question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Move away from null checks and .SingleOrDefault checks in controllers/handlers and instead rely on validating this information up front

See original GitHub issue

@BillWagner @MisterJames @tonysurma @sanilpaul @stevejgordon @VishalMadhvani

This is an overall pattern I’ve noticed in allReady, where every time we go to query something, we’re always allowing the return of nulls and then building subsequent conditionals to handle those nulls in consuming code. This additional overhead of checking for nulls carries into our unit tests, and makes our classes do more work than they have to.

For an example, let’s take the following files:

  • AllReady.Areas.Admin.Controllers.CampaignController
  • CampaignSummaryQueryHandlerAsync
  • associated unit tests

When CampaignController sends CampaignSummaryQueryHandlerAsync, the user is requesting an edit action method which will return to them a screen where they can change information about the Campaign:

Here is the controller code:

public async Task<IActionResult> Edit(int id)
{
    var viewModel = await _mediator.SendAsync(new CampaignSummaryQueryAsync { CampaignId = id });
    if (viewModel == null)
    {
        return NotFound();
    }
...

Here is the handler code:

public async Task<CampaignSummaryViewModel> Handle(CampaignSummaryQueryAsync message)
{
    CampaignSummaryViewModel result = null;

    var campaign = await _context.Campaigns
        .AsNoTracking()
        .Include(ci => ci.CampaignImpact)
        .Include(mt => mt.ManagingOrganization)
        .Include(l => l.Location)
        .Include(c => c.CampaignContacts).ThenInclude(tc => tc.Contact)
        .SingleOrDefaultAsync(c => c.Id == message.CampaignId)
        .ConfigureAwait(false);
...

When, under any circumstance, would we not be able to find the Campaign we want to edit by id? How is this id provided? It’s provided by a previous query/action method on the controller.

The way that campaign got into the system is we had a page that allowed a user to create a campaign, and validated everything up front before the creation was allowed. Basically, the UI enforces this. The Campaign should be there when we go to list all Campaigns, and it should also be there when a user picks a Campaign from this list to edit.

Sure, you could cobble together a URL like this: \Admin\Campaign\Edit\5

and paste it into the address bar of your browser and hit enter, but if someone is doing that, they’re most likely up to no good. So why are we optimizing the user experience to be friendly (we’re going to return a nice NotFound() result) for someone is most likely up to no good?

The answer is we shouldn’t. We should be optimizing for the happy path.

That being said, here is an example of additional unit tests we’ve been adding to the system to test these null returns:

public class CampaignAdminControllerTests
{
    [Fact]
    public async Task EditGetReturnsHttpNotFoundResultWhenViewModelIsNull()
    {
        CampaignController sut;
        MockMediatorCampaignSummaryQuery(out sut);
        Assert.IsType<NotFoundResult>(await sut.Edit(It.IsAny<int>()));
    }
}

I’d like to see a fundamental shift if this practice to optimize for the happy path. The chances of a user going to edit a Campaign that they just loaded 3 seconds ago from and Index action method being missing is extremely low. The only way I could see that happening is if some other user deleted the same campaign while they were in middle of their workflow, and again, I see this being very low.

Thoughts?

Issue Analytics

  • State:open
  • Created 7 years ago
  • Comments:12 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
mgmccarthycommented, Feb 5, 2017

@hodorswit, thanks for the input.

cc @stevejgordon @tonysurma @shahiddev @BillWagner @MisterJames

This is something we definitely can deal with using ActionFilters and ExceptionFilters, but like you said, if might not be apparent to new developers that this “magic” is happening in the filters, event though most .net developers are familiar with the framework-supplied filters that we see decorated controller classes and action methods.

That being said, one of the goals of the project (partially b/c it’s open source), is to optimize for the new contributor “F5” experience (where one can pull, then stand up the system and start running it immediately) as well as keep the cognitive load as low as possible to enable people that want to contribute at a very shallow level (for example, writing unit tests), or go deep (for example, invoking web-based API’s, figuring out the contract, wrapping it in an abstraction, etc…)…

The more and more I think about this Issue, I know believe it is tied to Issue #873, which is our ongoing discussion of what logging framework to use in production.

It seems that an Azure-based logging might when out over my proposal of ELMAH.IO, b/c it has native support for the application insights and telemetry that .NET Core provides.

But either way, regardless of the logging platform (ELMAH.IO/Azure logging), that logging framework automatically should be able to take exceptions that are not handled, log them for you, allow you define a compensating action (in production, re-direct to generic error page).

On the back-end, the logging framework should have built in dashboards as well as hooks for email/sms notifications to go out letting people know something went wrong, so in a way.

So, in summary, a mature logging framework already accomplishes what Action and Exception filters would by hand.

That being said, that does not rule out us taking the Action/Exception filter approach, as this approach still has it’s merits. We could even inject and ILogger<T> instance into these filters to be more explicit about exceptions we’d like to capture, what “level” of errors that are (info, warn, etc…) and how they should be logged.

1reaction
stevejgordoncommented, Sep 22, 2016

@mgmccarthy My initial opinion is that I prefer the current approach. Yes it adds a bit of code, but ultimately we are coding for all possible cases and ensuring we return a valid and suitable response. I agree the scenarios which could lead to controllers being hit with invalid id’s should be very limited but it’s the web and it’s still a possibility.

In your example, what would we return instead of null from the handler in the case that an invalid id was provided (either intentionally or due to some unforeseen issue/bug elsewhere)? What does the controller do as it’s expecting something back from the handler. If for any reason we haven’t got something to give it, what will the controller return?

I’ve not found it particularly painful to code with the current style and it is then explicit in what’s expected to happen.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Null check chain vs catching NullPointerException
Catching NullPointerException is a really problematic thing to do since they can happen almost anywhere. It's very easy to get one from a ......
Read more >
If functions have to do null checks before doing the ...
In summary, should methods be "data validating" and then do their processing on the data, or should it be guaranteed before you call...
Read more >
Best way to prevent redundant null checks further down in ...
My most common null error now is forgetting to initialize an Optional<T> with the empty(), or passing null value to parameter that is...
Read more >
Strict null checking the Visual Studio Code codebase
In this post, I'd like to share a major engineering effort that the VS Code team recently completed: enabling TypeScript's strict null checking...
Read more >
Optimize for the Happy Path: Null Checks - Michael McCarthy
This post will walk through an MVC application and talk about how to use null checks effectively. Un-needed null checks can obfuscate business ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found