Ma felmerült, hogy vajon elfogadtam-e egy pull requestet, avagy elutasítottam?

  • Még nem ratifikáltam…

…válaszoltam.

Megnézve a szó pontos definícióját az nem teljesen illik a helyzetbe, de nem is teljesen rossz oda:

ratifikáció
főnév, A ratifikáció az arra meghatalmazott állami szerv által kötött nemzetközi szerződés vagy külállammal kötött kétoldalú megállapodás utólagos jóváhagyása a törvényhozás (Magyarországon az Országgyűlés) által. […]

Forrás: Wikipédia

Megnéztem a Galaxis Őrzői 2 című filmet, hogy nektek már ne kelljen!

Tegnap felmerült, hogy menjünk moziba. Amikor odaértünk, akkor épp ez volt a következő film. Alapvetően utálom a képregényes tömegfilmeket, de az első részt még nézhetőnek találtam, amikor egyszer megnéztem jobb teendő híján, úgyhogy gondoltam adjunk egy esélyt.

A film annyira jópofáskodó, üres klisék sorozatából, illetve kiszámítható események sorozatából állt, hogy majdnem elaludtam. Körülbelül 3 helyen lehetett volna fordulat benne, amiket persze, ismerve a Hollywood-képregény tengelyt tudtam, hogy nem tesznek bele.

Olyan jeleneteknél hangzott fel hangos nevetés a közönség soraiból, ahol olyan kínosan éreztem magam, hogy ha a folyosó mellett ültem volna, felkelek és kimegyek…

Egy lehetséges váratlan fordulat lehetett volna, ha a félkegyelmű szereplőre bízott mega-giga-bombán a szereplő rossz gombot választ-e jelenetben (az azonnal meghalunk gomb vagy időzítve robban és happy end gomb közül kell választani, és korábban mindig rosszul csinálta). No nem arra gondolok, hogy megnyomja a rossz gombot, mindeki meghal, szomorú vége van a filmnek, idő előtt, és a galaxis nincs megőrizve, vagy ha mégis, akkor őrzők nélkül (és potenciális folytatás nélkül). Ilyet nyilván nem tenne Hollywood: egy ilyen termékben a vajon a kritikus helyzetben egyedül hagyott félkegyelmű miatt korai szomorú vége lesz-e a filmnek? (és a franchisenak?!?!?!) kérdésre a válasz egyértelműen nem! Ha a kötelező, elcsépelt dramaturgiai elemeket nem rakják be, csupán a félkegyelmű kivételesen nem csinál hülyeséget (hisz a folytatások!), lassítás és drámai zene nélkül… nos, az számomra egy meglepő – bár csupán dramaturgiai – fordulat lett volna. Csöppet sem izgultam, mivel csakis jót választhat, és ezt még egy óvodás is tudja, de akkor ezt minek ez a felhajtás? Ehelyett nem csupán megjósolható a következő jelenet, de drámai zenével lassítva kell végignéznem, ahogy váratlanul és az előzményekre rácáfolva a jó gombot nyomja meg. 😒 Eközben hallani, ahogy a közönség visszafolytja a lélegzetét, és a gomb megnyomásánál felszabadult sóhaj hallatszik a közönség soraiból. Összenézünk… Ez most komoly?

A film végül várható és unalmas véget ért, kötelező tanulságokkal, kötelező “karakterfejlődéssel”, ami persze az elmaradhatatlan folytatásban resetelve lesz, hogy a következő rész végére ugyanezt a karakterfejlődést előadhassák.

A film után a lépcsőn lefele sétálva egy fiatalember és egy idős hölgy közti furcsa párbeszéd tanúi voltunk:

  • Fiatalember: Jó estét! Ismerhetem önt valahonnan? Nagyon ismerősnek tűnik…
  • Idős hölgy (sejtelmesen): Talán a túlvilágról…

A furcsa beszélgetés szöget ütött a fejembe. Mi van, ha van reinkarnáció? Mi van, hogyha valahonnan a téren és időn túlról érkeztem ide? Lehetséges, hogy ott már láttam ezt a kiváló filmet, és azért találtam unalmasnak?

Értékelésem: 210. Nem ajánlott. Torrentről sem.

A minap egy kolléga (legyen a fedőneve mondjuk Charlie Firpo) egy érdekes rejtett “finomságra” hívta fel a figyelmem egy refaktorálás tapasztalataiból, a hiba megfejtése után. A probléma magjára redukálva könnyen érthető, de termelésben azért nehezebb volt megtalálni a eredendő okot. Ezt fogom bemutatni az úri közönségnek!

Írjunk egy kis kódot, hogy legyen mit refaktorálni!

Szerencsére nem kell sok a jelenség bemutatásához. Egy metódus, és egy teszt hozzá épp elég lesz:

public string Example(List<char> input){
    input.Reverse();
    return string.Join("", input);
}

[Fact]
public void Test(){
    var input = new List<char>() {'a', 'b', 'c'};

    Assert.Equal("cba", Example(input));
}

Itt örömmel konstatálhatjuk, hogy a metódus működik, amit az alapos 🙂 tesztje is kiválóan bizonyít!

Azonban minden kód életében eljön az idő, amikor refaktorálják! Tegyünk így mi is: minek az a List? Ne a konkrét típustól függjünk, hanem az interfészétől, írjuk át IList-re! Nem csalás, nem ámítás, egy karakter a változás:

public string Example(IList<char> input){
    input.Reverse();
    return string.Join("", input);
}

[Fact]
public void Test(){
    var input = new List<char>() {'a', 'b', 'c'};

    Assert.Equal("cba", Example(input));
}

Meglepve konstatáljuk, hogy a tesztek nem futnak többé helyesen! Mi lehet az oka?

Extension method: a jó, a rossz, és a csúf BCL

A C# nyelv 3-as verziójával kapta meg az extension method nevű funkciót, amire a LINQ támogatásához volt szükség. Ez a funkció meglévő típusok kibővítését teszi lehetővé, leszármazás nélkül, legalábbis látszólag. Valójában statikus metódusokról van szó, és némi szintaktikai édesítőszerről, mellyel a saját kódunkban ezeket a statikus metódusokat olyan szintaxissal hívhatjuk, mintha a típus metódusai lennének. Így a meglévő kódokban sem kell változtatni, de mintegy *mixin*ekkel dekorálhatjuk a típust saját kódunkban, ha a megfelelő névtér using bejegyzésével aktiváljuk őket az adott fileban.

Ez sok helyzetben vitathatlanul hasznos lehet. Nincs konszenzus a funkció megítélését illetően, de tény, hogy előnyei mellett egyes helyzetekben buktatókat is tartogat.

Hogyan is működnek az extension methodok?

Létrehozunk egy metódust, egy static class-ban, aminek első paramétere egy this kulcsszóval kerül megjelölésre, és annak a típusára tapad rá a bővítmény, a példányokra. A metódus természetesen sima static method szintaxissal is meghívható, de a példány-szintaxissal hívás a dolog értelme. Egy példa:

using System.Collections.Generic;
using System.Linq;

namespace Kodfodrasz.Blog.Example {
    public static class ExtensionMethodExample {
        public static string StringifyAndJoin <T>(IEnumerable<T> enumerable, string separator){
            var stringified = enumerable.Select( i => i.ToString());
            return string.Join(separator, stringified);
        }
    }
}
// be is kell ám kapcsolni a felhasználás helyén!
using Kodfodrasz.Blog.Example;

public void Main(){
    var arr = new []{1, 2, 3};
    var joined = arr.StringifyAndJoin(",");
    Console.WriteLine(joined)
}

A kimeneten pedig megjelenik, hogy 1,2,3. Ez elég jó, úgy tudtunk egy praktikus funkciót hozzáadni más típusához, hogy sem azt, sem más kódot nemkellett megbolygatni! (Amiket meg sem tudnánk egy könnyen tenni…)

Hogyan működik ez? A fordító a hívás helyén olyan IL kódot generál, ami a statikus metódust hívja. Ha pedig a típusnak van azonos szignatúrájú metódusa, akkor azt hívja a fordító. Az extension methodok közül a referencia típusa szerint legkonkrétabbat hívja a fordító, így lehet(ne) pl. a int IEnumerable<T>.Count() hatékonyabb overloadja az int IList<T>.Count().

Itt már gyanús kezd lenni a dolog! Valami ronda dolog van a BCL-ben elásva…

A csúf igazság

Vizsgáljuk meg a problémában érintett metódusokat!

Az eredeti kódban hívott metódus szignatúrája: void List<T>.Reverse() megfordítja a listán belül az elemek sorrendjét, mutálja a gyűjteményt! Az új kódban pedig az public static IEnumerable<TSource> Reverse<TSource>(this IEnumerable<TSource> source) metódus hívódott, ami egy fordított nézetet ad a módosítatlan gyűjteményről!

Itt elkerekedhetne az ember szeme: hiszen a visszatérési típusok teljesen más, nem is ugyanaz a szignatúra! Azonban C#-ban a visszatérési típus nem a szignatúra része, ahogy return type covariance sincs (sajnos, de egyszer talán eljön). Így azonban a két metódus azonos szignatúrájúnak számít, így a konkrétabb híváshoz “köt” a fordító az első, és a lazábbhoz a második esetben. Az első esetben a hívás mellékhatását használjuk ki, a második esetben a hívás eredményét eldobjuk, és a mellékhatás híján más eredményt kapunk, mint számítunk.

Hogy lehetett volna ezt kiküszöbülni? Nem szabadott volna azonos szignatúrájú, teljesen mást eredményező nevű metódusokat rakni legalább a BCL-ben levő gyűjtemények “fölé” az öröklési láncba. Hogy kellett volna ezt a metódust elnevezni? Bár dolgokat elnevezni nehéz, de talán a Reversed név az interfészen jó lett volna. Nekem azt is sugalmazza, hogy nem azt a példányt kapom, amin hívom, míg a Reverse az valamennyire sugalja, hogy azt a példányt módosítja. Ráadásul mivel egy szekvencia potenciálisan végtelen is lehet, ezért nem biztos, hogy megfordítható, így nem az IEnumerable<T> hanem az IList<T> típusra raktam volna csak rá a Reverse() műveletet.

Mit tanulhatunk ebből?

  • Használjunk olyan programnyelvet, amiben ha nincs értékül adva/felhasználva egy visszatérési érték, vagy nincs explicit jelölve, hogy nem érdekel minket, akkor az a program le se forduljon! Ha nem ilyen nyelvben dolgozunk (C# 😏), akkor használjunk statikus analízist, ami jelzi ezeket a potenciális hibákat!
  • Ne írjunk extension methodokat, ha csak nincs rájuk szükség!
  • Figyeljünk a metódusok elnevezésére, és a visszafele kompatibilitásra, különösen az extension metódusaink elnevezésére! Mi ne ássunk efféle csapdákat könyvtáraink felhasználóinak!
  • Tegyük extension methodjaink olyan névtérbe, hogy mindig explicit kelljen bekapcsolni őket!
  • Írjunk unit-teszteket! Soha ne refaktoráljunk amíg nincsenek tesztek az érintett kódra!

Még egyszer szeretném megköszönni Charlie Firpo kollégámnak a történetet, aki ezzel motivált, hogy jobban utánanézzek az extension methodoknak!

A LINQPad elégedett felhasználója vagyok évek óta, azonban nagyon hiányolom az autocompletot. Mivel azonban elsősorban nem adatbázisozásra, hanem C# kóddal kísérletezgetésre használom, így nem vásároltam meg a licencet, hisz van az igényeim lefedő ingyenes, és nyílt forráskódú alternatíva: a RoslynPad!

A RoslynPad egy egyszerű, még eléggé kezdetleges eszköz, mely autocomplete funkcionalitása a Roslynra, kódszerkesztője pedig az AvalonEditre épül. támogatja a #r szintaxisú nuget függőség referenciákat is, így kényelmesen írható benne mindenféle C# script.

Telepíthető a Windows Storeból is!

Get it on Windows 10

Javaslom kipróbálását!

Az Úr 2017. évében Magyarország az USA előtt jár valamiben. Miközben a HackerNews szilícium-völgyi olvasó azon keseregnek, hogy a lobbisták nyomására nincs könnyű és ingyenes adóbevallás bezzegamerikában, addig mi szépen utolérjük a 10 évvel ezelőtti Írországot. A tavaly kiváltott új típusú személyimmel a regisztráció az ügyfélkapura 5 perc volt (4 perc előkeresni a kapott azonosító adatokat tartalmazó dokumentumot). Az adóbevallás tartalmának megvizsgálása után láttamoztam azt, mivel a hatóság az amúgy is rendelkezésükre álló adatokból elkészítette nekem. Egy perc alatt meg is voltam! Az adóm 1%-ának felajánlása is sima ügy volt. Edge böngészőben ugyan a egy kicsit bugzott az 1%-os oldal, de ez akár lehet az Edge hibája is. Különcségem levetkőzve Chrome alatt pikk-pakk befejeztem.

Az egész megvolt kevesebb mint 10 perc alatt, azzal együtt, hogy fel kellett kelnem a gép elől először a személyi igazolványért, aztán a kiegészítő iratokért, aztán az adókártyáért. Elégedett vagyok.

Ha a kormány mindent legalább ilyen minőségben kivitelezne, és persze használná az Anti-lop-ot, akkor én már csak annyit kérnék, hogy a szóvicceket mellőzzük a közigazgatásban a jövőben!