Klasa Guard, sprawdzająca czy parametry są nullami

0

Szukam czegoś, co pozwoli mi na łatwe sprawdzanie czy parametry metody są nullami. Najprostsze podejście to oczywiście:

public void Method(ObjectA a, ObjectB b, ObjectC c)
{
    if(a == null) throw new ArgumentNullException("a");
    if(b == null) throw new ArgumentNullException("b");
    if(c == null) throw new ArgumentNullException("c");
}

Powyższe jest dla mnie trochę zbyt długie, więc poszukałem innych:

public void Method(ObjectA a, ObjectB b, ObjectC c)
{
    Contract.Requires<ArgumentNullException>(a != null);
    Contract.Requires<ArgumentNullException>(b != null);
    Contract.Requires<ArgumentNullException>(c != null);
}

Nadal długie. To, co mnie zainteresowało, to koncepcja klasy Guard:

public void Method(ObjectA a, ObjectB b, ObjectC c)
{
    Guard.ArgumentNotNull(a, nameof(a));
    Guard.ArgumentNotNull(b, nameof(b));
    Guard.ArgumentNotNull(c, nameof(c));
}

public static class Guard
{
    public static void ArgumentNotNull(object o, string name)
    {
        if(o == null)
            throw new ArgumentNullException(name);
    }
}

Nie sprawdzałem czy się kompiluje, ale mniejsza z tym. Czy podejście z klasą Guard ma jakieś istotne wady? Jak dla mnie jest to bardziej eleganckie rozwiązanie niż robienie bloków ifów z throwami, aczkolwiek obawiam się że mogę nie dostrzegać czegoś istotnego.

0

Trochę mało kodu, ale może wzorzec projektowy Object NULL mógłby być zastosowany, ale to zależ czy brak działania może przejść.

0

Eh, dopiero co przeczytałem o wzorcach projektowych i już ktoś potrzebował takiej pomocy.
I to nie pierwszy raz takie coś się dzieje.

Eh, cały świat się kręci wokół mnie :)

0

.

  • czy te ify występują w jednej funkcji (jeśli tak to nie ma sensu nic robić z tym, tylko dać te ify, skoro i tak musisz sprawdzić jakiś warunek)?
  • czy te ify powtarzają się takie same w różnych funkcjach (jeśli tak to może wydzielić jakąś funkcję isCosTamCorrect, która by sprawdzała te 3 warunki)? Albo zrobić asercję, czyli coś takiego jak napisałeś.
  • czy na pewno potrzeba sprawdzać czy coś jest nullem, czy nie skorzystać z wbudowanych mechanizmów języka (np. w JavaScript, ale pewnie i w innych językach nie da się zrobić:
var o = null;
o.foo();

bo wyskoczy wyjątek.

  • a być może weryfikacja danych powinna nastąpić na innym poziomie abstrakcji (tak żeby funkcja dostawała już sprawdzone dane)
  • a może weryfikacja jest niepotrzebna w ogóle? (czasem się sprawdza coś, co nigdy nie będzie prawdą, poza jakimiś skrajnymi przypadkami, które nigdy nie nastąpią)
  • a może da się to zrobić na poziomie analizy statycznej (czemu kompilator ci nie powie, że wrzucasz nulla do funkcji? Przecież podobno C# to statyczny język (sam piszę w JS, na którego wszyscy narzekają, że łatwo napisać coś z błędami, które dopiero wyjdą po uruchomieniu, więc myślałem, że w C# czy Javach nie ma takich problemów).

Czyli ciężko wyrokować coś o samych ifach w funkcjach, które i tak nic nie robią, przydałby się jakiś bardziej konkretny przykład.

0

Generalnie większość metod w projekcie jest taka, że tak czy siak jak wrzuci się nulla to gdzieś będzie ten NullReferenceException. Rzecz w tym, że na SO widziałem sporo komentarzy że mimo wszystko lepiej nie czekać aż null w końcu gdzieś tam zaskoczy i wyrzuci exceptiona, tylko od razu sprawdzić i ewentualnie dać ArgumentNullException. To drugie ma chyba nawet swoją nazwę, "Fast fail". Ma to ponoć związek z czytelnością, tj. od razu wiadomo który parametr się wykoleił.

0

PostSharp ma asserty które są wklepywane w IL po kompilacji. Dajesz sobie na klasy na które ma wygenerować i bajlando

3
EntityPamerano napisał(a):

Nie sprawdzałem czy się kompiluje, ale mniejsza z tym. Czy podejście z klasą Guard ma jakieś istotne wady? Jak dla mnie jest to bardziej eleganckie rozwiązanie niż robienie bloków ifów z throwami, aczkolwiek obawiam się że mogę nie dostrzegać czegoś istotnego.

To jest dość często stosowane rozwiązanie, powszechne także w wielu projektach open source. Wadą jest to, że masz dodatkową metodę w call stacku tego wyjątku.

Jest jeszcze coś takiego: https://github.com/Microsoft/CodeContracts

LukeJL napisał(a):
  • czy na pewno potrzeba sprawdzać czy coś jest nullem, czy nie skorzystać z wbudowanych mechanizmów języka (np. w JavaScript, ale pewnie i w innych językach nie da się zrobić:
var o = null;
o.foo();

bo wyskoczy wyjątek.

Na tym polega ten problem właśnie.

  • a być może weryfikacja danych powinna nastąpić na innym poziomie abstrakcji (tak żeby funkcja dostawała już sprawdzone dane)
  • a może weryfikacja jest niepotrzebna w ogóle? (czasem się sprawdza coś, co nigdy nie będzie prawdą, poza jakimiś skrajnymi przypadkami, które nigdy nie nastąpią)

To zazwyczaj złe podejście. Lepiej zabezpieczyć się na kilku poziomach niż dostać NullReferenceException z którego nic nie wynika i nieraz ciężko go odtworzyć.

  • a może da się to zrobić na poziomie analizy statycznej (czemu kompilator ci nie powie, że wrzucasz nulla do funkcji? Przecież podobno C# to statyczny język (sam piszę w JS, na którego wszyscy narzekają, że łatwo napisać coś z błędami, które dopiero wyjdą po uruchomieniu, więc myślałem, że w C# czy Javach nie ma takich problemów).

Ale statyczny system typów zabezpiecza przed użyciem tekstu tam, gdzie jest wymagana liczba, a nie przed brakiem wartości, który pojawia się dopiero w uruchomionym programie.

0

Pobawiłem się Code Contracts (kontraktami? Chyba tak to się tłumaczy) i jestem zachwycony, nie tyle samą możliwością zrobienia Contract.Requires, co statyczną analizą. Z tym że mam jeszcze dwie kwestie:

  • w których miejscach wrzucać sprawdzanie parametrów? Załóżmy że piszę libkę, więc mam w zasadzie trzy opcje: w metodach dostępnych na zewnątrz libki dla usera, we wszystkich publicznych metodach (przyjemne dla testów jednostkowych), lub też we wszystkich metodach publicznych i prywatnych. Osobiście skłaniałbym się ku opcji drugiej, to jest we wszystkich metodach publicznych.
  • w jaki sposób testować? czy wystarczy zrobienie czegoś takiego?
public void Method(ObjectA a, ObjectB b, ObjectC c)
{
    Contract.Requires<ArgumentNullException>(a != null, nameof(a));
    Contract.Requires<ArgumentNullException>(b != null, nameof(b));
    Contract.Requires<ArgumentNullException>(c != null, nameof(c));
}

//...

//Test 1
Assert.Throws<ArgumentNullException>("a", () => x.Method(null, b, c);
//Test 2
Assert.Throws<ArgumentNullException>("b", () => x.Method(a, null, c);
//Test 3
Assert.Throws<ArgumentNullException>("c", () => x.Method(a, b, null);

Używam XUnit do testów.

1 użytkowników online, w tym zalogowanych: 0, gości: 1