Music to Code By – czy to naprawdę productivity tool?

DotNetRocks słucham już ponad dwa lata. Przez ponad 200 podcastów dużo się dowiedziałem, zmieniłem swoje podeście do pracy i pogodziłem się z godzinnym dojazdem do pracy (tak jest! w jedną stronę!). Mimo to byłem sceptyczny gdy Carl Franklin reklamował swój projekt Music to Code By jako doskonałe narzędzie wspomagające produktywność. Jestem wielkim entuzjastą GTD, wszystko notuję w OneNote i chętnie sprawdzam na sobie wszelkie techniki i narzędzia wspomagania produktywności. Ponieważ jednak moje pierwsze dwa podejścia do pomodoro technique  zakończyły się fiaskiem, nie bardzo kupowałem muzykę, która wspiera tą technikę. Słuchając nagadywania trzy razy w tygodniu, patrząc na to co dostaję dzięki podcastowi za zupełną darmochę stwierdziłem: czemu nie. Pięćdziesiąt dolców za 9 utworów (wtedy było 9) co do których nie jestem przekonany, wydawało mi się dużo. Z drugiej strony czasem więcej wydaje się na gierkę lub na imprezę po której tylko kaca mogę przynieść do domu. Więc stało się. Kupiłem całą kolekcje i po kilku miesiącach obcowania z albumem moje wnioski wyglądają następująco.

Pomodoro Technique

Cały koncept music to code polegał na przygotowaniu utworów wspomagających wejście w tak zwane Flow i wspieranie pomidorowej techniki. Technika ta zakłada, że swoją pracę dzielimy na krótkie i intensywne iteracje. Jeden pomidor to 25 min pracy nad jednym taskiem w zupełnym skupieniu. Bez twittera, faceboka, skype’a i każdej innej rzeczy która nas wybija z rytmu. Po 25 min warto zrobić 5 min przerwy. Słysząc o pomodoro zawsze zakładałem, że 25 min to zbyt krótko, że po tym czasie dopiero się rozkręcam. Próbując stosować technikę pojawiła się kolejna niedogodność. Okazuje się, że w open spejsie bardzo trudno jest nie być rozpraszanym w trakcie pracy. W domu też nie jest łatwo.

Pomidor a teoria zarządzania czasem.

Jak pisałem już w komentarzu do posta o kanbanflow, miałem kiedyś okazję uczestniczyć w zajęciach na temat zarządzania czasem. Zgodnie z teorią głoszoną przez wykładowcę każde wejście na wysokie obroty podczas pracy nad pojedynczym, nietrywialnym taskiem zajmuje nam 15 minut. Każde rozproszenie natomiast zeruje ten licznik. Nie wiem czy to autosugestia,  czy samospełniająca się przepowiednia, ale u mnie tak to właśnie działa. Jeżeli już się wciągnę w coś to lobię popracować dłużej. Dlatego 25 min pomidora mnie odstraszało. Nie ma jednak żadnej przeszkody żeby zrobić trzy pomidory pod rząd i dopiero zrobić sobie przerwę.

Co daje Music To Code by?

Teraz do konkretów. Kupiłem masę empetrujek, więc trzeba było zacząć tego „używać”. Zacząłem od dłubania w domu i tu pierwsze zalety. Po pierwsze uświadomiłem sobie, że do tej pory trochę oszukiwałem. Puszczając sobie muzyczkę i kodując szybko odkryłem, że zaczynam odświeżać twittera, zerkać na skype, allegro, a utwór nadal gra. Ba! Nawet nie jest w połowie. Nie minęło to moje 15 minut. Szybko się przekonałem, że muszę popracować nad skupieniem się nad tym co robię.

Kolejna zaleta, która szybko wypłynęła to pomiar czasu. Robiąc swoje zajawki raczej nie planuję ile mi to zajmie, nie mam budżetu czy deadline’ów. Staram się jednak notować (lub przynajmniej być świadomym), ile czasu poszczególne czynności mi zajmują. Jest jednak z tym problem. Pracując nad wszystkim na raz trudno jest powiedzieć, że dokładnie 3 godziny spędziłem na kodowaniu. Puszczając 25 minutowe utwory pojedynczo, w kolejności i pauzując przy każdej przerwie (nie oszukujmy jest lepiej, ale przerwy zawsze będą) wszytko zaczyna mieć ręce i nogi. Skończyłem jakiś feature, coś zaimplementowałem,  coś skonfigurowałem. Ile to zajęło? No policzmy zacząłem od ścieżki 8, właśnie skończyła się 10. Trzy razy pół godziny daje mi półtorej. As simple as that.

Szybko też przeniosłem swoje zwyczaje do pracy i tu pojawiła się kolejna zaleta. W pracy niespecjalnie słuchałem muzyki. Okazuje się, że puszczając muzykę za bardzo się wsłuchuję. Z drugiej strony w pokoju panuje niepisana zasada nałożonych słuchawek. Nie masz słuchawek na uszach – można cię zagadnąć. Dochodziło do takich paradoksów, że zakładałem słuchawki nie słuchając absolutnie niczego. Niestety słyszałem wtedy rozmowy, a w niektóre lubię się włączyć. Music to code by dało mi tą wygodę, że słyszę dźwięk zagłuszający biurowe szumy, który przez 25 min nie zmienia swojego rytmu, intensywności, melodii. Po pięciu minutach staje się ignorowany.

Podobno same utwory są stworzone wg wytycznych co do częstotliwości i melodii wspomagających koncentrację. Nie wiem na ile rzeczywiście tak jest, nigdy mnie nie ciągnęło żeby tej wiedzy szukać, ale na pewno poprawa wydajności jest dla mnie zauważalna. Odnotowałem też, że jak każdy dobry projekt przyrostowy, music to code by z każdą iteracją stawał się lepszy. Projekt wystartował od kilku utworów, zbierane były feedbacki i co jakiś czas były wypuszczane kolejne utwory. Te późniejsze są przesłuchiwane częściej, łatwiej mi przy nich pracować.

Sumup

Podsumowując. Myślę, że zakup okazał się trafiony. Podejście trzecie do pomodoro technique może być podejściem udanym. Zachęcam też do śledzenia projektu i twitter Carl’a, bo trafiło się tak, że w marcu nowy, dwunasty track był udostępniany za darmo. Zakładałbym, że promocja może się powtórzyć.

 

 

 

Napisano w .net

NUnit – TestCaseSourceAttribute

Chciałem pokazać działanie kilku ciekawych atrybutów w NUnit. Długo się zastawiałem które są najciekawsze, w jakiej formie je sprzedać, jakich przykładów użyć i na co zwrócić uwagę. Doszedłem do jednego wniosku: za długo się zastanawiam. Poniżej pierwszy atrybut, który często mi się przydaje, a może nie każdy jest świadom jego istnienia. Wiedza ze standardowej dokumentacji NUnit 2.6.4 (najnowszy NUnit ostatnio nie współpracował z resharperem, lub odwrotnie) okraszona kilkoma przykładami i przemyśleniami. Przykład może jest odrobinę naciągany, ale powinien fajnie obrazować co chcę osiągnąć.

Punkt wyjścia

Będziemy sobie testować implementacje interfejsu ISelectSourceService, która to ma dostarczać różne słowniki do select listy.

    public interface ISelectSourceService
    {
        Dictionary<int, string> GetCountries(Lang lang, bool withEmpty0 = false);
    }

Zacznijmy od testowania domyślnego wywołania GetCountries. Chcemy wiedzieć, że dla każdego języka dostajemy nie pusty słownik, niepustych wartości. Jakie mamy opcje.

Możemy sobie napisać test dla każdej wartości lang, ale podejście trochę słabe. Mnożymy kod, a poza tym możemy przegapić pojawienie się nowego języka. W takim razie napiszmy bazową metodę testującą i wywołajmy w teście dla wszystkich wartości enum’a Lang. Coś w ten deseń:

    [Test]
    public void GetCountriesShouldReturnNotEmptyDictionary()
    {
        ISelectSourceService service = new SelectSourceService();
        TestForLanguage(service, Lang.Polish);
        TestForLanguage(service, Lang.English);
        TestForLanguage(service, Lang.German);
    }

Fajnie, kodu mniej ale jak się wysypie nie sprawdzi pozostałych wartości, a jak nie przypilnujemy to nie wiemy, która wartość psuje test. Nadal przegapiamy pojawienie się nowej wartości enum. Możemy być sprytni, wyciągnąć wszystkie elementy enuma i uruchomić TestForLanguage dla każdego elementu. Podejście dobre i złe:). Dzięki temu mamy jeden test, nie przegapimy nowego enum’a,  ale gdy któraś wartość powoduje błąd to przerywamy test i nie sprawdzamy reszty.
Innym podejściem byłby test z paroma testcase’ami.

    [TestCase(Lang.Polish)]
    [TestCase(Lang.English)]
    [TestCase(Lang.German)]
    public void GetCountriesShouldReturnNotEmptyDictionary(Lang lang)

Wtedy rezultaty wyglądają lepiej, ale nadal boli mnie to że mogę przegapić pojawienie się nowego języka i mam już tego dosyć.

TestCaseSourceAttribute

Z pomocą przychodzi nam TestCaseSourceAttribute, który pozwala podać źródło naszych testcase’ów i przetestować każdy case nawet gdy któryś się poczerwieni.

Właśnie to jest rozwiązanie problemu, który kiedyś strasznie mnie dotykał. W TestCaseSourceAttribute możemy sobie, utworzyć kolekcję do przetestwania. Innym problemem w którym mi się to przydało jest ładowanie test case’ów z pliku json lub innego źródła. Korzytając z tego atrybutu testowanie nasze może zaprezentować się następująco:

        [TestCaseSource("LanguagesTestCases")]
        public void GetCountriesShouldReturnNotEmptyDictionaryForAnyLanguage(Lang lang)
        {
            ISelectSourceService service = new SelectSourceService();
            var received = service.GetCoutries(lang);
            Assert.NotNull(received);
            Assert.IsNotEmpty(received);
        }

        public static IEnumerable LanguagesTestCases()
        {
            var enums = Enum.GetValues(typeof(Lang));
            return enums;
        }

Tym sposobem jeżeli w samej implementacji pojawi się nowy element, który powinien być przetestowany, TestCase zostanie utworzony automatycznie.
Trochę mnie boli jeszcze prezentacja pojedynczych test casesów, ale jest akceptowalna. Rozwiązanie tej bolączki zaprezentuje w następnym przykładzie.

Sam parametr sourceName musi spełniać kilka warunków.

  • Musi to być nazwa pola, property lub metoda
  • Musi to być instancja lub element statyczny
  • Musi zwracać IEnumerable lub obiekt implementujący IEnumerable
  • Pojedyncze itemy muszą być kompatybilne z sygnaturą metody testującej.

TestCaseData

Ciekawostką i rozwiązaniem wcześniej opisywanego problemu jest zwracanie elementów typu TestCaseData. Obiekt ten pozwala zdefiniować oczekiwany rezultat, wyjątek czy nazwę i opis test case’a. Dla przykładu mając property informujący czy trójkąt jest prostokątny możemy to testować w ten sposób.

        [TestCaseSource(nameof(TestCases))]
        public void IsRectangularShouldReturnProperValue(Triangle triangle, bool expectedResult)
        {
            var isRectangular = triangle.IsRectangular;
            Assert.AreEqual(expectedResult, isRectangular);
        }

        public static IEnumerable TestCases()
        {
            yield return new object[] {new Triangle(5, 4, 3), true};
            yield return new object[] {new Triangle(5, 4, 4), false};
            yield return new object[] {new Triangle(5, 2, 2), false}; //this one throws exception

        }

Podejście to sprawia jednak parę problemów.

  1. Nie możemy oczekiwanego rezultatu ustawić jako rezultat metody
  2. Nie możemy w jednym TestCaseSource sprawdzić czy dla złych danych (nie tworzących trójkąta) rzucany jest odpowiedni wyjątek.
  3. Test explorer nie pokazuje nic przyjemnego jako pojedyncze case’y. Jeżeli klasa TriangleEdges nie przeładowała ToString() wrzuca domyślny ToString()[screen1].
  4. Nawet jeśli przeładowaliśmy ToString() to:
    • nadal może nie być nasze oczekiwane rozwiązanie [screen2],
    • wymusza implemetacje ToString() pod testy.
screen1

Screen1

screen2

Screen2

Wszystkie te bolączki rozwiązują obiekty TestCaseData, przy pomocy których test mógłby wyglądać następująco:

        [TestCaseSource(nameof(TestCaseDataCases))]
        public bool IsRectangularShouldReturnProperValue2(Triangle triangle)
        {
            return triangle.IsRectangular;
        }

        public static IEnumerable TestCaseDataCases()
        {
            yield return new TestCaseData(new Triangle(5, 4, 3))
                .Returns(true)
                .SetName("Case for true");
            yield return new TestCaseData(new Triangle(5, 4, 4))
                .Returns(false)
                .SetName("Case for false");
            yield return new TestCaseData(new Triangle(5, 2, 2))
                .Throws(typeof(ThisIsNotTriangleException))
                .SetName("Case for exception");
        }

Test explorer też prezentuje się dużo lepiej.

screen3

Screen3

Oczywiście sam przykład mocno uproszczony i naciągany, ale pogląd na sprawę daje.
Mam nadzieję, że jest to zalążek wiedzy, która okaże się przydana i zainspiruje do przejrzenia dokumentacji NUnit.
Warto.

Napisano w .net, Nuget Tagi: , , ,

Nie tylko DotNetRocks warte posłuchania…

Początkowo planowałem napisać o podcastach DotNetRocks i wychwalić autorów pod niebiosa (bo należy im się za to co i jak robią). Tylko, że to chyba nie ma sensu. Linki do audycji przewijają się przez przeróżne blogi i wystarczy napisać, że naprawdę warto ich posłuchać. Ponieważ jednak dojazd do pracy zajmuje mi godzinę, a samych dotnetrocks’ów pojawia się tylko trzy odcinki w ciągu tygodnia rozglądam się, słucham, sprawdzam i myślę, że warto podzielić się wykopaliskami. Oto kilka podcastów, których słucham regularnie:

hanselminutes.com – to jeden z trzech podcastów wypuszczanych przez Scott’a Hanslelman’a. W sumie z całej trójki wg mnie najlepszy. Regularnie publikowane odcinki traktujące o przeróżnych zagadnieniach mniej lub bardziej związanych z branżą IT. Bitcoin’y, kreatywność, design, security, wirtualna rzeczywistość, web, mobile, IOT, czego tylko dusza zapragnie. Żadnej konkretnej technologii, żadnego wciskania microsoftu na siłę.

developeronfire.com – stosunkowo młody podcast (aktualnie 43 odcinki), który prowadzi Dave Rael. Każdy odcinek to praktycznie wywiad z doświadczonym developerem przeprowadzany wg bardzo podobnego scenariusza. Z każdego odcinka dowiesz się o między innymi początkach kariery, porażkach, sukcesach, polecanej książce, źródle wiedzy i dostaniesz 3 najistotniejsze rady dla developera. Pytania nie są specjalnie wyszukane, ale warto posłuchać jak różne refleksje i podejście mają ludzie do podobnych zagadnień, działający w tej samej branży. Świetna audycja na drogę powrotną z pracy. Pozwala mi wykrzesać jeszcze odrobinę motywacji i zapału do działania po powrocie, których nie miałem wychodząc z biura.

Hello World Podcast to podcast o tym jak obecnie odnoszący sukcesy liderzy, mentorzy, autorytety czy autorzy książek zaczynali w tym biznesie. Shawn Wildermuth  pyta swoich gości o najwcześniejsze doświadczenia z IT, o pierwszy komputer, pierwsze projekty, miejsce pracy i pierwszych mentorów na rozpoczynającej się ścieżce kariery. Często dygresje prowadzą do dyskusji o podejściu do branży, kariery, przewidywanej przyszłości i nadziejach związanych z branżą. W sumie odkąd jest developeronfire.com to trochę odcinków odpuściłem, ale warto było słuchać o tym jak różne ścieżki prowadzą do kariery programisty.

Zaczynając słuchać podcastów czekałem na pierwszą polską inicjatywę. Pierwszy i na razie jedyny polski podcast jaki trafił na moją playlistę to devtalk.pl Macieja Aniserowicza. Odcinki techniczne przeplatane „miękkimi”. Super zrobiony temat „Kariery programisty z Pawłem Zdziechem„. Warto było posłuchać o gamedev z Przemysławem Czatrowskim, który współtworzył najnowszego Wiedźmina. Dowiedzieć się jak inny jest to świat w porównaniu z składaniem kolejnych biznesowy aplikacji. Podcast na pewno warty do dopisania do listy dla wszystkich, którzy nie stawiają tylko na język angielski (chociaż i w devtalk trafiła się konkretna pogadanka o CQRS z Udi Dahan).

Wiem, że pojawił just4fun.io ale jeszcze nie zdążyłem przesłuchać.

Jest jeszcze cała masa innych podcastów, które warto sprawdzić. Do tej pory głównym źródłem moich informacji o nowych projektach były blogi i (a jak) dotnetrocks.com. Ostatnio nadziałem się jednak na fajne źródło na github’ie: awesome-geek-podcasts. Nawet udało mi się uzupełnić repo o developeronfire. Myślę, że warto przejrzeć listę i sprawdzić parę pozycji. Jeżeli chcesz się podzielić refleksją na temat wymienionych projektów lub jest coś naprawdę warte polecenia, a ja w swej niewiedzy zupełnie to pominąłem, czekam na komentarz do wpisu.

 

 

 

Napisano w Rozwój Tagi: , ,

Proste template’y z DotLiquid

Po raz kolejny chciałbym podzielić się znaleziskiem z Nuget. Zdarzyło się, że potrzebowałem dostarczyć temlate’y wiadomości tak aby możliwe było ich przeredagowanie przez osoby bardziej twórcze. W sumie udało się odnaleźć parę rozwiązań, które mogłyby spełnić to wymaganie. Między innymi

Te i parę innych znalazłem na liście Top 20 NuGet packages for Templating

Po wstępnym rozpoznaniu tematu zdecydowałem się na DotLiquid. Jest to zportowana .Net’towa wersja popularnego (podobno) Ruby Liquid temlating language.
Zdecydowałem się głównie z braku czasu na konkretną weryfikację ale wyboru nie żałuję i na pewno rozwiązanie to będzie moim faworytem w przyszłości. Główne zalety, argumenty które do mnie przemówiły to:

  • Dostępność na nuget
  • Przyzwoita dokumentacja
  • Czytelność
  • Bezpieczeństwo – możliwość zachowania kontroli nad mapowaniem parametrów temlate’u.

Aby tym razem nie powtarzać przykładów z dokumentacji założyłem sobie takiego example case’a:
Chcemy udostępnić użytkownikowi możliwość otrzymywania mailingu z wybranych kategorii. Mamy więc klasy.

    [LiquidType("Name","Surname", "MailingCategories", "Weight", "Height", "Birthdate")]
    public class Mailing
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public float Height { get; set; }
        public float Weight { get; set; }
        public DateTime Birthdate { get; set; }
        public string Email { get; set; }
        public string Secret { get; set; }
        public Guid ConfirmationGuid { get; set; }
        public Guid RemoveGuid { get; set; }
        public List<MailingCategory> MailingCategories { get; set; }
    }
    [LiquidType("Name")]
    public class MailingCategory
    {
        public int Id { get; set; }
        public String Name { get; set; }
    }

Chcemy aby po wpisaniu się do subskrypcji użytkownik potwierdził swój wybór. Tworzymy więc temlate’a maila i metodę dostarczającą treść maila.

Witaj, 

Aby otrzymywać mailing ze strony spamsender.com potwierdź swoje zgłoszenie:

Twoje dane:
Imię:  {{mailing.Name | Upcase }}
Nazwisko: {{mailing.Surname | Upcase }}
Wzrost/Waga: {{mailing.Weight}} / {{mailing.Height}}
Data urodzenia: {{ mailing.Birthdate | date:"MM/dd/yyyy" }}

Dopisałeś się do grup:
{% assign categories = mailing.MailingCategories | Sort: 'Name' %}{% for category in categories -%}
{{category.Name}}
{% endfor -%}

Aby potwierdzić zgłoszenie kliknij poniższy link:
{{ConfirmationLink}}

Aby anulować swoje uczestnictwo kliknij poniższy link:
{{RemoveLink}}

{{Secret}}

Powodzenia,
spamsender.com
    public class MailingMailContentProvider : IMailContentProvider<Mailing>
    {
        private readonly String templateText;
        private readonly String linkRootPath;

        static MailingMailContentProvider()
        {
            Template.NamingConvention = new CSharpNamingConvention();
            Liquid.UseRubyDateFormat = false;
        }

        public MailingMailContentProvider(string templateText, string linkRootPath)
        {
            this.templateText = templateText;
            this.linkRootPath = linkRootPath;
        }

        public string GetMailContent(Mailing item)
        {
            Template template = Template.Parse(templateText);
            Hash hash = Hash.FromAnonymousObject(new
            {
                mailing = item,
                ConfirmationLink = $"{linkRootPath}/confirm/{item.ConfirmationGuid}",
                RemoveLink = $"{linkRootPath}/remove/{item.RemoveGuid}"
            });
            String result = template.Render(hash);
            return result;
        }
    }

Samego działania możemy się oczywiście spodziewać, ale dla pewności zweryfikujmy to prostym testem.

    [TestFixture]
    public class MailingMailContentProviderTests
    {
        private IMailContentProvider<Mailing> provider;
        private readonly string linkPath = "www.moromind.pl";
        [SetUp]
        public void Init()
        {
            string temlateText = File.ReadAllText(@"Templates\MailingConfirmationTemlate.txt");
            provider = new MailingMailContentProvider(temlateText, linkPath);
        }

        [Test]
        public void TestGetEmailContentTestShouldReturnContentWithPropertiesIncludes()
        {
            Mailing mailing = new Mailing()
            {
                Id = 1,
                Name = "Grzegorz",
                Surname = "Morawski",
                Email = "testemail@domain.com",
                Birthdate = new DateTime(1985,1,31),
                ConfirmationGuid = Guid.NewGuid(),
                RemoveGuid = Guid.NewGuid(),
                Height = 175F,
                Weight = 78F,
                Secret = "my secret text"
            };
            mailing.MailingCategories = new List<MailingCategory>()
            {
                new MailingCategory() {Id=1, Name = "Humor"},
                new MailingCategory() {Id=2, Name = "Programming"},
                new MailingCategory() {Id=2, Name = "Extreme sports"}
            };
            string confirmationLink = $"{linkPath}/confirm/{mailing.ConfirmationGuid}";
            string removeLink = $"{linkPath}/remove/{mailing.RemoveGuid}";

            string mailContent = provider.GetMailContent(mailing);
            StringAssert.Contains(mailing.Name.ToUpper(), mailContent);
            StringAssert.Contains(mailing.Surname.ToUpper(), mailContent);

            StringAssert.Contains(mailing.MailingCategories[0].Name, mailContent);
            StringAssert.Contains(mailing.MailingCategories[1].Name, mailContent);
            StringAssert.Contains(mailing.MailingCategories[2].Name, mailContent);

            StringAssert.Contains(confirmationLink, mailContent);
            StringAssert.Contains(removeLink, mailContent);

            StringAssert.DoesNotContain(mailing.Secret, mailContent);

            Console.Out.Write(mailContent);
        }
    }

Output testu wygląda następująco:

Witaj, 

Aby otrzymywać mailing ze strony spamsender.com potwierdź swoje zgłoszenie:

Twoje dane:
Imię:  GRZEGORZ
Nazwisko: MORAWSKI
Wzrost/Waga: 78 / 175
Data urodzenia: 1985-01-31 00:00:00

Dopisałeś się do grup:
Extreme sports
Humor
Programming

Aby potwierdzić zgłoszenie kliknij poniższy link:
www.moromind.pl/confirm/1d12c918-2c25-44c2-bad7-4d028888fc8e

Aby anulować swoje uczestnictwo kliknij poniższy link:
www.moromind.pl/remove/4c4bf2a9-39cd-4ef5-8ae5-cf4e9b804e9d

Powodzenia,
spamsender.com

Na tym prostym przykładzie widzimy, że możemy mieć kontrolę nad:

  • Dostępem do properties
  • Listami
  • Tagami
  • Formatem danych

Oraz paroma innymi rzeczami. Oczywiście to nie wszystko z podstawowych funkcjonalności, a  jako ambitni programiści możemy całość rozszerzyć o swoje filtry, tagi, block tagi, ale o tym innym razem (lub w dokumentacji).

Napisano w .net Tagi: , , ,

Kanbanflow – ciekawa implementacja tablicy tasków

Pracując w różnych projektach i śledząc blogi udało mi się zapoznać z wieloma wirtualnymi tablicami tasków. Do tej pory do prywatnych zastosowań najbardziej przemawiały do mnie Kanbanize i Trello. Oczywiście jest jeszcze Jira ale to już inna liga, inny kaliber. Po wykopaniu na Dotnetomaniak’u wpisu  „Dobre nawyki programisty C#” moją uwagę zwrócił Kanbanflow, umieszczony w linkach do posta. Sam tool swoją prostotą kojarzy się z trello. Pozwala tworzyć proste tablice tasków, dodawać kolumny i taski wraz ze szczegółami. Jest jednak kilka smaczków, które przekonują mnie do uruchomienia tego w jakimś mały projekcie.  Poniżej przedstawiam kilka feature’ów, które do mnie przemawiają.

Czas szacowany i faktyczny

W kanbanflow do każdego taska możemy przypisać Time spend i Time estimated. Przy odpowiedniej konfiguracji oba czasy są ładnie wyeksponowane na tablicy. Jest to jeden ze szczegółów, którego zawsze mi brakowało w trello. Kanbanize ma swoje Custom fields, ale w zupełności wystarcza mi to co dostaje w standardzie Kanbanflow.

Daty deadline

Kolejną fajną rzeczą w tool’u jest możliwość dodania na danym tasku daty deadline dla znalezienia się w zadanej kolumnie. Jeżeli data jest przekroczona to system odpowiednio oznacza karteczkę na tablicy. Feature o tyle fajny, że dla każdej kolumny taką datę można ustawić i zaplanować sobie planowany flow. Dodatkowo możemy ustawić notyfikację na dzień przed upłynięciem due date.

Pomodoro timer

Kolejnym dodatkiem, który może się spodobać, a na pewno zwrócił moją uwagę jest pomodoro timer. Kto stosuje i lubi metodę pomodoro nie musi sięgać po osobne narzędzie. Stoper pozwala skonfigurować czas pracy, krótkiej przerwy, dłuższej przerwy i zdefiniować co ile pomidorów mamy dłuższą przerwę. Nie jestem fanem tej techniki (a nawet jestem antyfanem), więc raczej mi się nie przyda.

Konfiguracja wyświetlania per kolumna

Pora na najważniejszy feature, który odróżnia kanbanflow i przekonuje mnie do dania mu szansy w projekcie, a więc konfiguracja tablicy. Autorzy sprytnie wydedukowali, że przecież nie na każdej kolumnie potrzebujemy dokładnie tych samych informacji. Narzędzie pozwala więc skonfigurować każdą kolumnę oddzielnie. Ja założyłbym sobie taką konfigurację.

  • Kolumna Backlog, na której wyświetlam tylko tytułu taksów.
  • Kolumna To do, na której wyświetlam dodatkowo szacowany czas, listę subtasków.
  • Kolumna In progress, która pokazuje maksymalnie dużo szczegółów. Na pewno chcę widzieć tytuł, szczegóły, osobę która realizuje, labelki i poszczególne czasy. W końcu tutaj są tylko te zadania, które faktycznie realizujemy i o których musimy wiedzieć najwięcej.
  • Kolumna Done, w której interesowałoby mnie tylko czas szacowany i faktyczny.

Kanbanflow_screen

W praktyce pewnie konfiguracja jeszcze się zmieni, więc tym bardziej dobrze wiedzieć, że mamy takie możliwości.

Wersja premium

To podstawowe rzeczy, które zwróciły moją uwagę. Wszystko to jest za free w podstawowej wersji. Gdy tool się spodoba to warto przemyśleć przejście na wersję premium. Za jedyne pięć dolców miesięcznie możemy dostać między innymi:

  • wyszukiwanie/filtrowanie
  • dodawanie tasków mailem
  • eksport/import danych
  • załączniki, dashboard, API
  • raporty wydajności
  • cztery role użytkowników

Wszystkie feature’y można doczytać na kanbanflow.com. Ja sam na razie do premium się nie garnę. Ale na pewno z wersją free bliżej się poznam.

Napisano w Tools

ManyConsole – pomysł na console application prosto z Nuget

Czasami zdarza się, że musimy dopisać do systemu prostego konsolowego tool’a pozwalającego na wykonanie kilku wybranych funkcjonalności. Czy to jakiś prosty raport tekstowy, sprawdzenie stanu, cokolwiek. Bywa, że z dumą przystępujemy do pisania „od zera”. Czasami mamy już swoje wypracowane rozwiązanie, a czasem ruszamy na poszukiwania. Tym szukającym właśnie wychodzi naprzeciw ManyConsole.

ManyConsole to biblioteka dostępna na nuget pozwalająca w prosty sposób rozwijać i zarządzać aplikacją konsolową. Używa i zależy od parsera NDesk.Options z tym, że główną wartością dodaną jest obsługa wielu komend z różnymi parametrami do czego przyzwyczaił nas git. Automatyczny help jest już wisienką na torcie:).

Zgodnie z instrukcją z GitHub aby użyć wystarczy:

  1. Dodać bibliotekę z nuget
  2. W main wywołać ConsoleCommandDispatcher
  3. Dodać wymagane komendy jako klasy dziedziczące z ConsoleCommand

Wystarczy zerknąć na przykładowy kod żeby zrozumieć co i jak.
W samym main w bazowej implementacji wystarczy nam tylko:

class Program
{
    class Program
    {
        static int Main(string[] args)
        {
            // locate any commands in the assembly (or use an IoC container, or whatever source)
            var commands = GetCommands();

            // then run them.
            return ConsoleCommandDispatcher.DispatchCommand(commands, args, Console.Out);
        }

        public static IEnumerable<ConsoleCommand> GetCommands()
        {
            return ConsoleCommandDispatcher.FindCommandsInSameAssemblyAs(typeof(Program));
        }
    }
}

Mamy więc ConsoleCommandDispatcher dzielnie poszukujący komend w Assembly.
Implementacja przykładowej komendy wygląda następująco:

    public class ExampleCommand : ConsoleCommand
    {
        public ExampleCommand()
        {
            this.IsCommand("Example", "Example implementation of a ManyConsole command-line argument parser Command");

            this.HasOption("b|booleanOption", "Boolean flag option", b => BooleanOption = true);

            //  Setting .Options directly is the old way to do this, you may prefer to call the helper
            //  method HasOption/HasRequiredOption.
            Options = new OptionSet()
            {
                {"l|list=", "Values to add to list", v => OptionalArgumentList.Add(v)},
                {"r|requiredArguments=", "Optional string argument requiring a value be specified afterwards", s => OptionalArgument1 = s},
                {"o|optionalArgument:", "Optional String argument which is null if no value follow is specified", s => OptionalArgument2 = s ?? "<no argument specified>"}
            };

            this.HasRequiredOption("requiredOption=", "Required string argument also requiring a value.", s => { });
            this.HasOption("anotherOptional=", "Another way to specify optional arguments", s => {});

            HasAdditionalArguments(2, "<Argument1> <Argument2>");
        }

        public string Argument1;
        public string Argument2;
        public string OptionalArgument1;
        public string OptionalArgument2;
        public bool BooleanOption;
        public List<string> OptionalArgumentList = new List<string>();

        public override int Run(string[] remainingArguments)
        {
            Argument1 = remainingArguments[0];
            Argument2 = remainingArguments[1];

            Console.WriteLine(@"Called Example command - Argument1 = ""{0}"" Argument2 = ""{1}"" BooleanOption: {2}", Argument1, Argument2, BooleanOption);

            OptionalArgumentList.ForEach((item) => Console.WriteLine(@"List Item {0} = ""{1}""", OptionalArgumentList.IndexOf(item), item));

            if (BooleanOption)
            {
                throw new Exception("Throwing unhandled exception because BooleanOption is true");
            }

            return 0;
        }
    }

Mam nadzieję, że tym krótkim wpisem zachęcę kogoś do wypróbowania ManyConsole albo przynajmniej zaznaczę obecność tej biblioteki w świadomości użytkowników nuget.

Napisano w .net, Nuget Tagi: , , ,