C#

Biblioteka Refit lepsza od RestSharp

Posted on Updated on

Po lekturze Exploring refit, an automatic type-safe REST library for .NET Standard i zamienieniu bibliotek w jednym projekcie mogę stwierdzić że Refit jest po prostu lepszy.

Tak wygląda korzystanie z niego:

public interface IGitHubApi
{
    [Get("/users/{user}")]
    Task<User> GetUser(string user);
}


var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com");

var octocat = await gitHubApi.GetUser("octocat");

RestSharp problem z deserializacją

Przy okazji jeśli wywala wam się serializacja w RestSharp’ie to może powodem jest brak domyślnego Json.NET – Newtonsoft serializera. Opis jak sobie z tym poradzić

Zapętlone projekty? – rozwiązaniem interfejs

Posted on

Mam projekt Persistence, który odpowiada za połączenie interfejsów repozytoriów z EntityFrameworkiem (Architektura heksagonalna). Dużą częścią tego projektu są SampleData. Są to dane, którymi seedujemy baze za starcie, aby można było wyklikać skomplikowane scenariusze na podczas Demo, oraz aby na nich można było odpalać UI testy w Selenium. (aplikacja pobiera dane z innych źródeł więc niemożliwe jest wypełnienie danych poprzez wyklikanie z przeglądarki).

Tak wyglądał kod, gdy wszystko było w jednym projekcie:

class MyContext : DbContext
{
    public DbSet<User> Users { get; set; }
}

class MigrationsConfiguration : DbMigrationsConfiguration<MyContext>
{
    protected override void Seed(MyContext context)
    {
        SeedConfiguration.Seed(context);
    }
}

class SeedConfiguration
{
    public static void Seed(MyContext context)
    {
        var user = new User
        {
            Id = 1,
            UserName = "John Galt"
        };

        context.Users.AddOrUpdate(x => x.Id, user);
    }
}

Ten SampleData był już osobnym katalogiem i zaczał z czasem bardzo rosnąć. Stało się jasne, że Persistence sam w sobie bardzo rzadko się zmienia (czasem jakieś migracje, nowe pola), czyli jest “stabilny”. Natomiast SampleData było żywe (nowe rzeczy, refactoringi itp).

Jest to więc znakomity kandydat do wydzielenia nowego projetku. I tutaj pojawił się problem. Persistence potrzebowało mieć referencję do SampleData, aby wywołać explicite metodę Seed(), a SampleData potrzebowało referencję do Persistence aby mieć DbContext na którym operuje.

Tego problemu nie zauważyłem od razu, dopiero na końcu wydzielania nowego projektu uderzyło mnie to. Cięzko było to obejść :/ Próbowałem coś w stylu ładowanie dynamiczne dll’ek z katalogu i skanowanie refleksja kto implementuję metodę którą chcemy wywołać – wszystko HACKy. Nie warto robić hacków więc zostawiłem temat. Po miesiącu rozwiązanie samo mnie uderzyło gdy wcale o nim nie myślałem 🙂 i był to nowy interfejs.

Kod gdy mamy dwa projekty i Persistence ma referencję do SampleData

// SampleData project
public interface ISampleDataContext
{
    DbSet<User> Users { get; set; }
}

class SeedConfiguration
{
    public static void Seed(ISampleDataContext context)
    {
        var user = new User
        {
            Id = 1,
            UserName = "John Galt"
        };

        context.Users.AddOrUpdate(x => x.Id, user);
    }
}

// Persistence project
class MyContext : DbContext, ISampleDataContext
{
    public DbSet<User> Users { get; set; }
}

class MigrationsConfiguration : DbMigrationsConfiguration<MyContext>
{
    protected override void Seed(MyContext context)
    {
        // works cause MyContext implements ISampleDataContext
        SeedConfiguration.Seed(context);
    }
}

Czyli eleganckie rozwiązanie zgodne z OOP.

Od siebie mogę dodać, że nawet jeśli wydzieleniu tego projetku było ważną rzecza (nie tylko ja widziałem tą potrzebę) to nie warto było robić z tego powodu dirty HACKA. Czasem lepiej poczekać, pogadać z różnymi ludżmi… Czasem ktoś nam poda dobre rozwiązanie a czasem ono samo do nas przyjdzie.

Niepisane (już spisane) zasady w projekcie C#

Posted on Updated on

Chciałem po prostu spisać dla potomnych rzeczy które dotychczas może nie były spisane.
Będą dopisywane nowe.

  • nazywanie zmiennej “_” oznacza, że ta zmienna jest nieistotna i pomijamy ją w dalszym kodzie, mimo że musimy jej użyć. Najczęściej pojawia się w lambdach, ale jest też kilka przykładów w pozostałym kodzie, np:

    static readonly ConcurrentDictionary<string, UserModel> LoggedInUsers;
    
    public static void Unsubscribe()
    {
        UserModel _;
        LoggedInUsers.TryRemove(Login, out _);
    }
    

* Przeczytana i zrozumiana książka Clean Code, Czysty kod
* R# nie sugeruje zamieniania na var.
* nie używamy ref i out.
* brak zakomentowanego kodu, jeśli coś kiedyś byśmy potrzebowal, to do tego służy system kontroli wersji (git)
* korzystanie z tooli (R#, StyleCop etc), jeśli jakaś reguła może być automatyczna to ludzie nie powinni tracić czasu na ręczne (mentalne) Code Review.

Option zamiast wzorca TryGetValue(…, out value)

Posted on

Przedstawię dziś klasę całkiem naturalną z punktu widzenia programisty funkcyjnego. Gdyby to przełożyć na język bliski naturalnemu to jest to konstrukcja, która “albo posiada wartość zadeklarowanego typu, albo tej wartości nie ma”. Posłużę się od razu przykładami.

Są przypadki, gdzie nie wiemy czy dostaniemy wartość czy nie. Klasyczny przypadek to parsowanie. Gdy korzystamy z wzorca TryGetX kod wygląda:

int value;
if(int.TryParse(currentLine, out value))
{
    // happy path
}
else
{
    // else path
}

Typ ten można przekazywać dalej do innych funkcji lub zwracać z aktualnej funkcji. Dzięki temu dalej w flow działania aplikacji nie potrzebujemy 2 różnych funkcji przyjmujących raz happy path a raz failure path. Ostatecznie na końcu i tak trzeba “rozpakować” tą wartość, ale po drodze mogą zdarzyć się na przykład funkcje logujące, albo obsługujące wspólnie “brak danych” jak np HTTP 404. W przykładzie z TryGetX() wiadomość o sukcesie mamy tylko

var maybeResult = GetResults(data);
if(maybeResult.HasValue == false)
{
    return HttpStatusCode.NotFound; // 404
}

Jak na przykład może wyglądać taka klasa (inne nazwy to Maybe):

Refactoring wyrzucający części niezmienne poza IFa

Posted on

W IF’ach powinniśmy robić tak mało jak to tylko możliwe, przykład:

if (HasInternalDocuments(AdditionalDocuments))
{
    AdditionalDocumentsGroup = new AdditionalDocumentsGroup(AdditionalDocuments);
}
else
{
    AdditionalDocumentsGroup = new AdditionalDocumentsGroup(new List<Document>());
}

To co mi się nie podoba jest zaznaczone poniżej na niebiesko. Jest to część która jest identyczna zarówno dla sekcji IF jak i ELSE. Dopiero kod podkreślony na czerwono jest różny w IF oraz ELSE i to jest miejsce którym powinniśmy “sterować” właśnie za pomocą instrukcji warunkowych.

Duplication inside IF statement
Duplication inside IF statement

Refactoring który wyrzuca to co jest “wspólne” poza instrukcję warunkową

IEnumerable<Document> internalDocuments;

if (HasInternalDocuments(AdditionalDocuments))
{
    internalDocuments = AdditionalDocuments;
}
else
{
    internalDocuments = new List<Document>();
}

AdditionalDocumentsGroup = new AdditionalDocumentsGroup(internalDocuments)

Jeszcze dalej można to zamienić na coś poniższego. Nie jest to już jednak istota tego refactoringu to syntactic sugar który nam daje .NET:

var internalDocuments = HasInternalDocuments(AdditionalDocuments)
    ? AdditionalDocuments
    : Enumerable.Empty<Document>();

AdditionalDocumentsGroup = new AdditionalDocumentsGroup(internalDocuments);

To jest taki minimalny przykład. Najgorsze są długie switche w których kopiowane są kilku linijkowe wyrażenia gdzie tak naprawdę wartość testowanej w switchu wartości ma tylko wpływ na jedną zmienną.

Refactoring prowadzi do dalszych usprawnień

Ten typ zmieniania kodu jest bardzo niedoceniany. Zazwyczaj nie chodzi tylko o to że zmienimy trochę kodu. BARDZO CZĘSTO prowadzi to do dalszych usprawnień, bo zauważyliśmy że:

  • inne warunki można rozpisać inaczej lub opuścić
  • kolejne duplikacje kodu można uwspólnić
  • w jakieś kawałki kodu nie wejdziemy nigdy

Generic memoization method (C#)

Posted on

Func<T, R> Memoize<T, R>(Func<T, R> func)
{
    var cache = new Dictionary<T, R>();
    return arg =>
    {
        R result;
        if (cache.TryGetValue(arg, out result))
            return result;

        result = func(arg);
        cache.Add(arg, result);

        return result;
    };
}

void Test()
{
    var powerMem = Memoize<int, int>(a =>
    {
        Console.WriteLine("{0}^2 is {1}", a, a * a);
        return a*a;
    });

    powerMem(1); // Wypisze tylko teraz na konsole
    powerMem(1);
}

void Test2()
{
    var addMem = Memoize((Tuple<int, int> arg) =>
    {
        Console.WriteLine("adding {0} + {1}; ", arg.Item1, arg.Item2);
        return arg.Item1 + arg.Item2;
    });

    addMem(Tuple.Create(1, 2)); // Wypisze tylko teraz na konsole
    addMem(Tuple.Create(1, 2));
}

Z książki Real-World Functional Programming rozdział 10.4.

Ścieżki bezwzględne w Nancy zamiast HttpContext.Current.Server.MapPath()

Posted on

Dzisiejszy bohater – IRootPathProvider.GetRootPath().

Zamiast

public HomeModule()
{
    string filePath = HttpContext.Current.Server.MapPath("/Pdf/VERDANA.ttf");
    // ...
}

Używam

public HomeModule(IRootPathProvider pathProvider)
{
    string filePath = Path.Combine(pathProvider.GetRootPath(), "Pdf/VERDANA.ttf");
    // ...
}

Po prostu lubię się pozbywać zależności od HttpContext.Current

Źródło: Nancy – The root path