Egy ideje látom, hogy nyitnom kell frontend irányba.
Az Angulart választottam a koherenssége miatt. Tudom mások pont ezt hozzák ellenérvként.
Képbe került olyan feladat, hogy hordozható készülékről kell intézni a fixen telepített számítógépes rendszer funkcióinak egy részét. Így kerül képbe a Progressive Web Application (PWA).

Kinéztem egy ideálisnak tűnő oktató videót. Végignéztem. Nem is volt hosszú, értettem is. Ugyan kissé gyorsnak találtam a bemutatás. Gondoltam, megállítom ahol kell.
A videó 20 perces, gondoltam egy nap alatt “megeszem”. Hát nem! Úgy öt nap lett belőle.
A videó négy éve készült azóta verziók változtak, plugin alakult át, inkompatibilitások keletkeztek. Persze az is közrejátszott, hogy Angular téren is kezdő vagyok.

Telepítettem, amit kellett Node Package Manager-t (NPM), Angular command-line interface-t (NG CLI). Visual Studio Code ide már volt.

Win10-en az NG futtatását engedélyezni kellett.

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Ez viszonylag megengedő. Aki paranoiás beállíthat szigorúbbat.

A videó által javasolt `Angular console` már nem létezik átalakult `Nx console`-á. Persze a kezelése is lényegesen eltér a videóban láthatótól.

A `square` komponens ugyan létrejött, de a `app.component.html`-ben a vscode nem ajánlotta fel és a fordítás is sikertelen volt. Biztos nálam van a hiba, figyelmetlen voltam, vagy a megváltozott NX konzolban rosszul paramétereztem. Jó pár kísérlet után rákerestem a Google-ben.
Az Angular 17-es verziója bugos (lehet szándékos a változás?). Minden esetre 2023 november közepén a létrehozott projektben nem keletkezett az `app.module.ts` állományt. Ami a például a komponenseket is láthatóvá teszi a sablonokban (template HTML).

Sajnos nem jegyzeteltem mindent. Így nem emlékszem pontosan, hogy `Nebular` kontroll készlet inkompatibilitása hogyan bukott ki. Minden esetre a Nebular fejlesztése egy ideje megszűnt, csak régebbi Angular verzióval használható.

Nos, itt adtam fel a videó szó szerinti követését előröl kezdtem a projektet NX console és Nebular nélkül. Kontroll készlet gyanánt a `@angular/material`-t használtam. Ami közel sem olyan látványos, viszont kompatibilis és működik.
A videó lépéseit, logikáját követtem, de az alábbi parancsokkal operáltam.

ng new Tic-Tac-Toe --no-standalone
cd .\Tic-Tac-Toe
ng serve //(1)
ng build --configuration=production
ng g c square -t -s
ng g c board
ng add @angular/material
ng add @angular/pwa
ng build --configuration=production --aot=true
ng serve --configuration=production
npm i inquirer-autocomplete-prompt@2 //(2)
npm install -g firebase-tools
firebase login
ng add @angular/fire
firebase experiments:enable webframeworks
firebase deploy --only hosting:tic-tac-toe-6b23b

(1) a vsCodeban a Tic-Tac-Toe folderben kiadva
(2) a friss verzió nem volt kompatibilis a firebase-hez

Mint látható, a Firebase telepítése sem volt zökkenő mentes, elakadt hibával. A telepítés paramétereinek beolvasását intéző plugin “túl friss” volt. A `inquirer-autocomplete-promp`-ból régebbi verzióra volt szükség.

A `firebase login` persze feltételezi, hogy legyen Firebase fiók. Mintha a projektet is létre kellet volna hozni a login előtt.

Az eredményt egy ideig elérhető lesz itt. Mivel PWA, telepíthető is. Windows alatt Chrome böngészőből. Androidon szintén Chrome-t használtam. Ismerős telepítette iPhone-ra, szintén Chrome-t használva. Iphone-on a jobb szélső gombok kilógnak a játékmezőből. Ez némi css munkát igényelne, de itt a telepíthetőség volt az elsődleges szempont.

A forrás elérhető a publikus repoban.

Azért piszkált a dolog. Megigazítottam, hogy iPhonon is "rendesen" nézzen ki.
A mat-disabled-button lógott ki a grid-template-columns-al definiált 3x3-as játéktér celláiból. Ennek okára nem jöttem rá. Nem értek ehhez a kontroll készlethez.  Mint korábban említettem az elfogadható kinézet mellet a "kvázi" alkalmazásként való telepíthetőség volt a cél.

Eddig akkor kezdtem így mondandómat, ha a Google keresőt kívántam használni informálódásra.

Kedves ismerősöm lelkesen ecsetelte a Chat GPT használatával szerzett tapasztalatait. Eddig elriasztott, hogy a kipróbáláshoz is fiókot kell létrehozni. Most akadt egy téma, amit úgy is gondoltam "megkutatni". Hát legyen...

Gyakran programtechnikai kérdéseimre keresem a választ a neteten.
Most is ebbe az irányba indultam.

Első menetben olyat kérdeztem Java / Hibernate témában, amire ismertem a választ. Elsőre nem voltam elég pontos, ezért általánosabb választ kaptam. A kapott kódot átnézve azt szakmailag megfelelőnek találtam. Pontosítás után azt a példakódot kaptam, amit vártam.

Másodszorra feltettem valós, "withe label" eszköz okosotthon-rendszerhez illesztésre vonatkozó kérdésemet. A kapott válasz az addig ismertekbe illeszkedett, de kissé általános volt, kódot nem tartalmazott. "Szemtelenül" megkérdeztem, kaphatok-e példaprogramot. Persze volt a válasz és jött a kód. A megoldás jónak tűnik, de csak része a teljes feladatnak. Ez a projekt az információ gyűjtés állapotában van, ezért a kód gyakorlati értékét csak a felhasználás során fogom megtudni.

Úgy látom, új "barátra" tetem szert. Használni is fogom. Jelentős időmegtakarítás lenne, ha a Google találatok közül nem kellene szemezgetnem. Amennyiben a Chat GPT-től kapott válasz nem fog tetszeni, még mindig ott a "régi barátom".

A Go egy procedurális nyelv, nem objektum orientált.
És még is találtam több az alábbihoz hasonló megoldást.

package main

import (
   "io"
   "os"
   "strings"
)

type rot13Reader struct {
   r io.Reader
}

func (rd *rot13Reader) Read(b []byte) (n int, e error) {
   n, e = rd.r.Read(b)
   for i := 0; i < len(b); i++ {
     c := b[i]
    if (c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M') {
      b[i] += 13
   } else if (c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z') {
     b[i] -= 13
    }
  }
  return
}

func main() {
   s := strings.NewReader("Lbh penpxrq gur pbqr!")
   r := rot13Reader{s}
   io.Copy(os.Stdout, &r)
}

Gyakran alkalmaznak ehhez hasonló megoldást a egység-tesztek során.

A Go-ban létezik a funkció

func function_name(Parameter-list)(Return_type){
   // function body.....
}

és a metódus.

func (reciver_name Type) method_name(parameter_list)(return_type){
   // method boby
}

Látható a különbség a reciver a fogadó. Azt olvasom itt: "A method in Golang is like a function that needs a receiver to get invoked. In simple words, a method is a function that is defined on struct types." Vagy is a funkció a reciver entitáson kerül végrehajtásra.
Hogy is van ez,
a metódus a fogadó objektumon van értelmezve?

Nézzük a fenti példát. A rot13Reader struktúra rendelkezik az r egy az io:reader interfészt kielégítő objektum memeberrel. (Egy procedurális nyelvben valami kielégít egy interfészt?) A definált Read metodus fogadója egy rot13Reader típusú objektum. A metódus első tevékenysége, hogy meghívja az r field(?) Read (figyelem) metódusát. Majd a kapott eredményt átdolgozza.
Az én értelmezésemben ez az io.Reader read metódusának felülbírálása (override).

Akkor a Go használható objektum orientált szemléletben?
A Java is használható procedurálisan, static metódusok  main metódusból való hívogatásával. A Java esetén érzem, hogy ez erőltetett. Nem is nagyon található ilyen érdemi példa. Míg a Go esetén lépten-nyomon látok az objektum orientáltságra hajazó megoldást.

Ahogyan az már kiderülhetett az utóbbi időben a Go nyelv tanulásával foglalkoztam. Persze a "becsípődött" tesztelést sem tudtam elengedni. Így amikor az első feladat produktummá érett, rátértem a tesztelésre.
Most rövid összefoglalót olvashattok, hogy mire jutottam eddig.
A Ginkgo a teszter, a Gomega a matcher.
A Ginkgo a dokumentációja szerint BDD (Behaviour Driven Development) eszköz, de a példákból úgy láttam, unittesztre is alkalmas.
 
A Ginkgo — Gomega párosra esett a választásom, mert a tesztelő kód szerkezete, olvashatósága kedvemre való. A kód valójában öndokumentáló, ha nem vagyunk restek a leíró paramétereket kellő részletességű információval kitölteni.
  
Most, hogy megvolt az első bevetés, rájöttem, hogy inkább a modul viselkedését teszteltem.
  
A feladat során adat lekérdezést, feldolgozást, megjelenítést kellett végrehajtani. 
A tesztelés során azt kell igazolni, hogy a “beszerzett”, feldolgozott, formázott adat logikailag és formailag megfelel az elvártnak. 
 
Mivel az adat harmadik féltől származik, el kell érni, hogy rendelkezzünk referencia adattal, amihez az aktuális hívás eredménye hasonlítható.
A feladat szerint aktuális adatot kell lekérni. A szolgáltatónál lehetőség van ugyanannak a querynek csak a dátum paraméterét cserélve archív adat lekérdezésére. A feltételezéssel élve, hogy az archív adat nem változik, mind a referencia előállításakor, mind a teszteléskor azonos dátumot használva a teszt végrehajtható. 
  
Mivel nem kizárható, hogy az archív adat mégis megváltozhat, két lépcsősre terveztem a tesztet. Első lépésben a kapott valasz nyers adatait ellenőrzöm. Másodikként a formázott eredményt. Így ha a nyers ellenőrzés hibára fut indokolt lehet, hogy a kimeneti adat is eltérő legyen. Amennyiben csak a második teszt bukik el, a hibát a feldogozás + formázás lépéseiben kell keresni.
  
A referencia adatokat a teszt tervezésekor beszereztem, állományba mentettem. A tesztelés során az aktuális eredmény kerül a tárolt referenciával összevetésre.
A feldolgozatlan nyers eredményben szerepel a végrehajtás időpontjának bejegyzése. Szükséges ezt mind a referenciából, mind az aktuális adatból eltávolítani, különben hibára fut a teszt. 
  
A feldolgozást az olvashatóság és újra felhasználhatóság érdekében kisebb eljárásokra tagoltam (clean code elvek szerint). Unit tesztelés esetén minden eljáráshoz készül(nek) teszt(ek). Ha a kimeneti adat tesztje elbukik, hasznos lehet, ha rendelkezünk az elemi lépések tesztjeivel is. Így a kritikus eljárás tesztje is hibára fut. Továbbá hasznos tudni, hogy a módosítás okozott e hibát a módosított eljárásban, vagy keletkezett e következmény hiba máshol.
  
Jelenleg még csak a két, viselkedést ellenőrző, teszt készül el.

A Go-ban elmélyedve persze keresem a TDD lehetőségeket.

A Go rendelkezik beépített unit teszt lehetőséggel. A JAVA-ban megszokott JUnit csomaghoz képest nehézkesnek érzem. Persze a JUnit plugin. A Go esetén is megtaláltam a Testify csomagot. Amelytől kezd a dolog hasonlítani a megszokotthoz. A Testify-ről még nem tudok eleget. Gondolom egy későbbi posztban előkerül.

Amit már kitapasztaltam, hogy az alap teszt készlet már tartalmazza a teszt-lefedettség vizsgálatot.
A `go test -cover` parancsot kiadva a tesztek végrehajtásán túl a teszt-lefedettség százaléka is megjelenítésre kerül.
Részletes információ két lépésben állítható elő. Először a
`go test -v -coverprofile cover.out ./YOUR_CODE_FOLDER/.` paranccsal egy leíró fájlt kell előállítani.
A `go tool cover -html cover.out -o cover.html` paranccsal a böngészőben jelenik meg a forrás "kiszínezve". Így sor pontossággal látható mire kell még teszt írni.

A Visual Studio Code (VSCode) képes elvégezni a lefedettség vizsgálatot és megjeleníteni eredményét. Bekapcsolni a `Ctrl+Shift+p` kombináció hatására megjelenő választékból a `Go: Toggle Test Coverage in Current Package` lehetőséget kiválasztva lehet.
A kód érintett sorait erőteljesen aláfesti zölddel illetve pirossal. Szerintem rontja a kód olvashatóságát. Úgy tűnik más is így van ezzel mert találtam olyan beállítást amely a sorszámok mellett függőleges, színes sávokat használ a jelöléshez.
A Beállításhoz be kell lépni a paraméter beállításba és beilleszteni a szükséges paramétereket.
`Ctrl+Shift+p` után `Prefferences: Open User Settings (JSON)` kiválasztva jutunk a JSON szintaktikájú paraméter szerkesztőbe. Ide kell a záró kapcsos zárójel elé beilleszteni az alábbit:

    "go.coverOnSave": true,
    "go.coverageDecorator": {
        "type": "gutter",
        "coveredHighlightColor": "rgba(64,128,128,0.5)",
        "uncoveredHighlightColor": "rgba(128,64,64,0.25)",
        "coveredGutterStyle": "blockgreen",
        "uncoveredGutterStyle": "blockred"
    },
    "go.coverOnSingleTest": true

A beillesztés előtt, a zárójelet korábban megelőző, sor végére vessző kerül.
A beállítás leírása itt található.

Felmerült, hogy Go nyelven fejlesszek. Ezért tanulmányozni kezdtem a nyelvet.

Amit feltétlenül tudni érdemes a Go nyelvről, hogy szigorúan típusos, procedurális, fordított nyelv. Sajátos tömör szintaktikával rendelkezik. Az előálló futtatható állomány környezet specifikus és "önjáró". Vagyis a Windows környezetben készült eredmény egy exe és minden függő modul ebben van benne.
Valamikor a pályafutásom elején sokat használtam a Clipper nyelvet. Mily érdekes a hasonlóság. Mennyire szerettem volna ha a Clipper típusos.

A webes keresésekkor ajánlatos a Go helyett Golang kifejezéssel keresni.

Alábbi megállapításaim a még nem kellő mélységű ismereteimen, de gyakorlati megfigyeléseimen alapulnak.

 A Go alapegysége a modul. Én ezt szeretném projektként értelmezni, hogy be tudjam illeszteni eddigi ismereteimbe.
A modul alapértelmezetten a felhasználói mappában "lakik" a saját mappájában. De ez nem kötelező. A Go workspace tetszőleges helyen lehet, csak a `GOPATH` környezeti változónak oda kell mutatni. Amennyire meg tudom állapítani, a mappa nevének a modul nevét kell viselnie. A modul mappájának van ajánlott szerkezete, bene ajánlott almappákkal. Az eddig látott példákban ennek használatát nem tatasztalom. A gyakorlat szerint a csomagoknak (package) van saját almappája.

A modul mappájában meg kell hogy legyen a `go.mod` állomány, amelyet az inicializáló parancs hoz létre. Szokásos még a `main.go` állomány ami a belépési pontot tartalmazza. Tapasztalatom szerint az állomány neve nem kötelező, de kötelezően a `main` csomagban kell legyen és definiálnia kell a `main` funkciót.

Fejlesztői környezetből nem látszik nagy választék. A Go oldalán a Jet Brains féle InteliJ változat, a Visual Studio Code (VSCode) és kissé meglepő módon a Vim található. A webes példákban gyakran a VSCode-t használják.
Mivel nálam a VSCode telepítve van és ha nem is túl gyakran, de használom. Ezt választottam.

Maga a Go "eltűri", hogy egy gyűjtő (workspace) mappában több, a fenti szerinti, modul (projekt) mappa legyen. Viszont a VSCode nem szereti ha a workspace mappában több `go.mod` állományt tartalmazó almappa van. Nem találja meg a másik package-ben található funkciókat és más hasonló "nyűgjei" vannak.
Szerencsére az 1.18 verziótól lehetőség van a `go work` használatára.
A workspace mappában állva kell a parancsokat kiadni. Először inicializálni kell a szolgáltatást a `go work init` paranccsal. Ezt követően lehet `go work use [folder-name]` paranccsal hozzáadni a projekteket. A folyamat eredménye képen létrejön a `go.work` állomány benne a projektek felsorolásával.
Ettől a VSCode már megtalál mindent.

 

 

Szívesen nézek magyar nyelvű előadásokat érdekes témákról. Mégis a magyarom a legerősebb. Az alapozó előadásokat jobban megértem magyarul.Így futottam bele a refaktorálásról szólóba:
Gémes Tamásman office worker Társalapító és CTO Wyze Fintech Startup Studio előadása a Szent-Györgyi Albert Agóra Informatórium-ban Clean Code: Refaktoráláscímmel

Az előadás Martin Fowler "Refactoring - Kódjavítás újratervezéssel" művét követi. A bejegyzés címét innen kölcsönöztem.
A könyv szerkezete szokatlan. Az alapozó ismeretek bemutatása helyett egy példával kezdődik. Ugyan ez a példa, nem definiáltam kata, de a lépésenkénti magyarázatok és a beillesztett kódok miatt követhető, végrehajtható úgy ahogyan az a katáknál szokásos.Végig is csináltam.
 
Mivel a kód részlet szerepét, feladatát nem ismertem, a folyamat erősen hasonlít olyan esetre, amikor más kódját kell karbantartani.
Ilyenkor az első lépés megérteni mit csinál a kód. Persze, ha már kitaláltuk, jó lenne valahogy rögzíteni, hiszem ha nem holnap kell újra hozzányúlni, lehet kezdeni előröl az egészet.
Kínálkozik a kommentelés lehetősége. A Clean Code elvek szerint a "miértek" kommentbe rögzítése elfogadott, de általánosságban kerülni kell a kommentezést. Akkor marad a kód emberi fogyasztásra alkalmassá tétele. Ha "csak" a változók és eljárások nevét változtatjuk beszédessé, már elkezdtünk refraktorálni.
 
A refaktorálás első lépéseként a hosszabb eljárásokat érdemes logikailag kisebb, beszédes nevű eljárások kiemelésével felbontani. Így jó esetben az eredeti eljárás a tennivalók felsorolására esik össze.
 
Ha a kiemelt eljárás gyakorta hivatkozik más osztály adott példányára, akkor annak abban az osztályban van a helye. Át is kell helyezni oda.
 
Magam is alkalmaztam a polimorfiát, ott ahol azonos funkciójú, de eltérő kivitelezésű perifériát kellet illeszteni a rendszerbe. Ismerem annak lehetőségét, hogy a típusosságból eredően a polimorf osztályok egyedei eltérően viselkedjenek az adott szituációban. Nem lepett meg, hogy ez kihasználható elágaztatás kiváltására, de nem emlékszem, hogy használtam volna már.
 
Sokat tanultam a példából. Az eljárásokat többnyire ismertem. De volt olyan amit még a gyakorlatban nem próbáltam. 
 
Ezt a szabadon elérhető anyagot találtam a GitHub-on.
Ajánlom a könyvet mindenkinek. Az első kiadás úgy 20 éve jelent meg 1.1-es Java kódokkal. A második néhány éve javascript kódokkal.
Most visszatérek az olvasáshoz.
Várakozásommal ellentétben a Gilded Rose kata eltér a korábban ismertetett katáktól.
A kód készen van. A feladat a refaktorálás. 
Emily Banche ezt három videóban mutatja be Part 1, Part2, Part3
A refaktorálásnak önmagában nem elengedhetetlen feltétele a teljes tesztlefedettség, de jelentősen megkönnyíti a munkát, mivel “egy gombnyomással” ellenőrizhető, hogy a beavatkozás megőrizte-e a helyes működést. A bemutatott technika nagy mértékben automatikusan hajtható végre, ha kihasználjuk a tesztlefedettség vizsgálat segítségét.

Az első részben a teljes lefedettségű tesztet készíti el Emily. Az eljárás érdekessége, hogy mindössze egy testeset kell ehhez, ha kombinált elfogadási teszt készül. (Az elfogadási tesztekről nem régen készült poszt.) Persze az elfogadási minták állománya 80 soros lett. Minden sor valójában egy eset.

 A második rész az első refaktorálás végrehajtását tartalmazza. Emily ehhez a ‘lift up conditional' eljárást alkalmazza. (Erről is van poszt.) Érdekes megfigyelni, hogy mennyire mechanikus az eljárás. Szinte csak a feltételek létjogosultságát kell eldönteni. Azokban esettekben amikor a feltétel mindig igaz, a hamis ágat lehet eltávolítani. Amikor mindig hamis az igaz ágat. Illetve, ha nincs hamis ág és a feltétel mindig igaz, a feltétel vizsgálatra nincs szükség.

 A harmadik videóban a ‘Replace Conditional with Polymorphism’ eljárással végez refaktorálást Emily. Ennek lényege, hogy az egyes feltétel ágak ugyan azon az objektumon végeznek eltérő változtatást. Ekkor az eredeti objektum kiváltható speciálisan viselkedő (működő) leszármazottaival.

A kata leírása itt található.

(Az informatikai polimorfizmus leírása itt található.)
A refaktorálás célja a kód átdolgozása, ésszerűsítése. 
Korábban már megtanultam, hogy az elágaztatás vagy a switch használata utal(hat) egy vagy több SOLID elv megsértésére.
A példák amit találtam switch kiváltására szolgálnak. Lényegében adott objektumon kell az adott feltétel szerint eltérő beavatkozást tenni. 
Kérdéses, hogy ez sérti e az egyetlen felelősség elvét, hiszen a feladat logikailag azonos.
Viszont a nyit/zárt elv (Egy osztály vagy modul legyen nyílt a kiterjesztésre, de zárt a módosításra.) alkalmazható az esetre. Hiszen bármelyik ág változtatási igénye esetén ennek az osztálynak a forrásában kell változtatni. Az adott objektumból származtatva létre lehet hozni az adott feladatot speciálisan kezelni tudó objektum osztályokat. Amelyek ebből eredően, csak rájuk vonatkozóan végzik el a tennivalókat. Itt mindjárt érvényesül a Liskov helyettesítési elv (Minden osztály legyen helyettesíthető a leszármazott osztályával anélkül, hogy a program helyes működése megváltozna.).
A szöveges példa és a videó anyag is támaszkodik az egységtesztekre illetve a teszt lefedettségre
Az ismertető részben már utaltam arra, hogy ez az eljárás, objektum leszármaztatással operál. A SOLID elvekből eredően, az adott objektumra vonatkozó tevékenység az adott objektumban megfelelő helyen van. 
(Zárójelben megjegyzem, szóba jöhet a stratégia tervezési minta használata is. Ekkor a stratégiákat megvalósító osztályok közös interfészt valósítanak meg. Így logikailag rokon az a megoldás. Illetve szolgáltató osztály is alkalmazható, ha a modell osztály tisztán tartása is cél. Ez utóbbi esetben ez az eljárás alkalmazható a szolgáltató osztály leszármaztatásával.)
A cél az, hogy minden az elágaztatási ághoz létezzen egy származtatott osztály amely speciálisan tudja elvégezni a feladatot.
A SOLID elvek érvényesítése mellett az elágazó szerkezet kitölti a metódus testét. Amennyiben az elágazó szerkezet mégis része egy eljárásnak, ki kell azt emelni metódusként.
Minden elágaztatott esethez létrejön egy a bázis osztálból származtatott osztály, amely felülbírálja az elágazást tartalmazó eljárást. A default ágban található kód marad az objektum eredeti osztályába. Elképzelhető, hogy valamelyik ágon nincs teendő, akkor abban az osztályban “üresen” kell implementálni a metódust. 
Az eljárás lényegéről itt olvashattok.
Célja a kód átdolgozása, ésszerűsítése. 
A folyamat használja a tesztlefedettség elemzést. Ami feltételezi, hogy a refaktorálás előtt rendelkezzünk mindent lefedő teszttel.
A code coverage elemző elérhető mind az Eclipse-ben mind a InteliJ-ben, de gondolom minden IDE-ben. Illetve létezik önálló készlet is. Jávához például a JaCoCo.
Az eljárás lépései:
  • A vizsgálandó kódrészlet kiemelése. Akkor is, ha ezzel az vizsgálandó metódus kiürül.
  • Az első felhozandó feltétellel (lift up condition) létre kell hozni egy if/else blokkot, melynek mindkét ágában meghívásra kerül az előző lépésben kiemelt metódus 
  • A feltétel mindkét ágába visszakerül a korábban kiemelt metódus tartalma. Így mindkét ág redundánsan tartalmazza az átdolgozandó kódot. A kiemelt majd visszaillesztett metódust meg kell szüntetni.
  • A lefedettség vizsgálat segítségével meg kell szüntetni a mindig hamis ágakat. Illetve egyszerűsíteni kell a mindig igaz ágakat. Közben a meglévő tesztekkel rendszeresen ellenőrizni kell, hogy a kód helyes maradt e.
  • Ennek eredményeként az első igaz ágban a csak feltételt kezelő kód marad. Ezt ki kell emelni metódusba.
  • A hamis ágban maradt kóddal el kell végezni a fenti folyamatot.

Az eljárás végén az átdolgozandó metódus az elsődleges feltételek vizsgálatait és az azokhoz tartozó feldolgozó metódusok hívását fogja tartalmazni.

Persze szükség lehet az így létrejött feldolgozó metódusok hasonló refaktorálására. 
Két videót ajánlok Gregor Riegler: Lift Up Conditional Refactoring és Emily Banche: Refactoring item logic using ‘lift up conditional'. Mindkét videó azonos példakódot használ, a GildedRose katát.

Mint láthatjátok mostanában sokat foglalkozom TDD-vel.
Kezdetben minden ellenőrzéskor lefuttattam az összes tesztet. A kódolási gyakorlatokról készült videókban azt látom, hogy amíg az aktuálisan fejlesztett teszteset nem lesz elfogadott (zöld) addig nem futtatják az összes tesztet, csak utána. Az IntelliJ esetén a margón van erre ikon. Eclipse-ben nem találtam ehhez plugint sem. Ellenben találtam hot-key-t.

A teszteset deklarációjában a metódus nevén kell állni.
Egy test eset futtatása shift + alt + x , t
Egy test eset debuggolása shift + alt + d , t
A katák gyűjtő helyének számító oldalon található leírás szerint nem látszott mennyire szerteágazó a feladat.
A feladat lift szimuláció implementálásával. Két megoldást is találtam. az egyik előre definiált API interfésszel és kiegészítő osztályokkal és vizuális szimulátorral rendelkezik. A másik esetben a nincs API interfész, de megvannak kiegészítő osztályok. A működés visszajelzése szöveges eredmény állományba történik. 
Maga a feladat soktétű. Elsőre nem tűnik túl nehéznek. Jó néhány teszt eset kell hozzá. 
Ennek a gyakorlatnak talán lényege is a megfelelő tesztesetek megfogalmazása.
Vizuális lift szimulátorral rendelkező esettanulmány.
A videót két részletben, külön helyeken találtam meg. A második rész vége mintha nem a befejezés lenne, mert a feladat félbemarad. Harmadik részt viszont nem találtam hozzá. A sikertelen teszt esetet megvizsgálva nem találtam túl nagy problémát. Gyakorlatilag egy sor és némi formázás hiányzott. 
A videót nem találtam olyan szórakoztatónak mint az eddig megnézetteket. Az előadó (Victor Rentea) hadar és kapkodik. A tesztesetek nevét többször átírja, nem tűnik eléggé összeszedettnek. Jelentős fejlesztői múltja van, nem gondolnám most találkozott ezzel a katával. 
Az előadó a lift felhasználói visszajelzéseivel kezdi a munkát, ennek kapcsán foglalkozik a működtető eljárásokkal. Számomra fordítva lenne logikus. A kódból úgy láttam a kijelzés a működést nem befolyásolja. Ezt a teszt is igazolta.
Forrás: victorrentea / kata-lift-java
Videdeók: első rész , második rész.
A LiftController osztály létrehozásakor az automatikus @Override javítandó a videó szerint. Akkor a szimulátor is elindul.
Szöveges állományok használatán alapuló esettanulmány
Egészen más a megközelítés. Itt a működési logika megalkotásával, kiegészítésével kezdenek. Ahogyan azt vártam is.
A megvalósítás az Approval Tests könyvtárat használja. Valamely objektumok (nem feltétlenül java értelemben) összehasonlításán alapul az elfogadás. Jelen esetben szöveges állományba kerültek kiírásra a rendszer által felvett állapotok. Az elvártat tükröző állapotátmeneteknek megfelelő állomány kerül összehasonlításra az aktuális eredménnyel. Az eljárás előnye, hogy az állapotot leíró tulajdonságokat nem kell egyenként validálni, hanem azok egyetlen folyamatban vizsgálhatóak. 
Forrás: emilybache / Lift-Kata
Szöveges leírás itt található
Videó itt található
Eddig elfogyasztott kata gyakorlatokban közös volt, hogy a “mesteren” érezhető volt az adott feladat ismerete. Nem röptében találták ki a megoldásokat. Bár mindkét előadó megfelelő múlttal rendelkezik végig éreztem rögtönöznek. 
Most már úgy ítélem meg ez a feladat egyáltalán nem kicsi. Abból is látszik, hogy sem a kétszer egy óra, sem az egyszer másfél nem volt elegendő a teljes végrehajtáshoz.
Mindamellett elleshető mit csinálnék hasonlóan és másképpen. Az approvals könyvtár megismerését igen hasznosnak találtam.
ctrl + 1 megnyitja a “quick assist” helyi-menüt
eclipse_filed.png
Ott kiválasztható a field létrehozása.

Ez egy rövidke kata.

Bemenő numerikus adatot át kell alakítani az  alábbiak szerint. 
A hárommal osztható számok esetén a “Fizz” szöveget, az öttel oszthatóak esetén a “Buzz” söveget, a hárommal és öttel is osztható számok esetén a “FizzBuzz” szöveget kell visszaadni. Egyéb esetekben a számot string-é alakítva (pl.: 1 → “1”).
Két érdekességet láttam. 
  • Általános esetben a szám string-é konvertálásához a String.valueOf(input) használja. Ami valójában az Integer.toString(i). Mivel ez ekvivalens bármelyik használható.
  • Az oszthatóság vizsgálatakor a sorozatos feltételvizsgálat helyett a enumeráción futó for each ciklust vezet be. Logikailag hasonlóan a román számok generálása katában használthoz. Csak ott két párhuzamos tömbön megy a ciklus. Lehet itt is “olcsóbb” lenne tömböt használni, bár az Enum használata beszédesebb.
Ehhez a katához csak videót találtam.
Szomszédot lenyűgöző pixeles fénypanel vezérlő szoftver készítése.
Lehet nem végeztem még elegendő katát. Lehet ez a természetes. Meglepett, hogy a megoldó leírásban szó sincs arról, hogy hozzál létre kiegészítő osztályokat a feladat végrehajtásához, míg az elsődleges osztály esetén erre kitér a szerző. A bemutatott kódban minden ismertetés nélkül megjelenik az ismeretlen osztály ismeretlen metódusára való hivatkozás. Bevallom feltúrtam a netet, biztosan létezik ilyen, csak én nem ismerem. Idővel rájöttem, ennek a létrehozása is a feladat része. Ezután már nem lesz meglepő, ha belefutok ilyenbe. A dolog érdekessége, hogy később ennek a kiegészítő osztálynak a bővítése re-fraktorálása már megjelenik az útmutatóban. Továbbá a másik segéd osztály létrehozására kapunk utasítást.
Néhány sarokpont:
  • A panelt a kódban reprezentáló két dimenziós tömböt TDD nélkül már az osztály kezdeti létrehozásakor definiálnánk. A TDD elveit követve ez csak akkor következik, be amikor először szükség van a panel egy “pixelére”. 
  • Hasonló a helyzet a pixelt reprezentáló Light osztály státuszát (bekapcsoltságát) jelző változó esetén. Akkor hozzuk létre amikor először kell hivatkozni rá.
  • A lambda kifejezések okán ismertem a FunctionalInterface fogalmát, ebben a körben való használatát. Most számomra ismeretlen használatát tanulhattam meg. 
  • A panel meghatározott területeinek “on”, “off” és “toggle” kapcsolására egy-egy formailag azonos metódus szolgál. Melyek dupla ciklus belsejében hívják meg a Light objektum vonatkozó metódusát. Kiemelésre került a dupla ciklus egy olyan metódusba, melynek egyik paraméter Consumer<Light>. Híváskor ebbe a paraméterbe kerül átadásra a végrehajtandó műveletet megvalósító metódus hivatkozás, Light::turnOn. Részletesebben:

public void turnOn(CoordinatePair coordinatePair) {

    eachLight(coordinatePair, Light::turnOn);

}

public void eachLight(CoordinatePair coordinatePair, Consumer<Light> consumer) {
   ...
}

  • Ezt a megoldást igen “szellmesnek” találom. E nélkül a formailag azonos metódusok egységesítése most csak valamilyen elágaztatási megoldás jut eszembe. Azt pedig a Clean Code elvek szerint ellenjavallt.
Ez is olyan gyakorlat amely során aha élményem volt.
Leírása itt található.

Az Eclipse-ben mint a legtöbb IDE-ben van lehetőség fejlesztést könnyítő kód minták (templates) gyors beillesztésére.

Mint tapasztalhatjátok, mostanában sok tesztet írok. A neten fellelhető tanácsok hatására rászoktam az AssertJ könyvtár használatára. Számomra sokkal olvashatóbb ez a szintaxis. A mit, mivel hasonlítunk össze szemléletesebb.
Az Eclipse-ben van template a JUnit V5-höz, de az AssertJ-t nem ismeri.

Tehát nálam a teszt eset template így néz ki:

@${testType:newType(org.junit.jupiter.api.Test)}
public void test${name}() throws Exception {
      ${staticImport:importStatic('org.assertj.core.api.Assertions.*')}
      assertThat().isEqualTo();
}

Az AssertJ is tud egy teszt esetben több elvárást kiértékelni, de ezeket össze kell fogni egy SoftAssertions-ba. Ekkor a template ilyen:

@${testType:newType(org.junit.jupiter.api.Test)}
public void test${name}() throws Exception {
     ${Import:import('org.assertj.core.api.SoftAssertions')}
     SoftAssertions assertions = new SoftAssertions();
     assertions.assertThat().isEqualTo();
     assertions.assertThat().isNotEqualTo();
     assertions.assertAll();
}

 AssertThat ellenőrzésből is lehet több egy tesztesetben, de az első hibánál leáll a folyamat és a többi feltétel nem értékelődik ki.
SoftAssertions használatakor az összes feltétel kiértékelődik. Ciklusban használva vigyázni kell, mert 10-20 kiértékelési lépés után jelentősen lassul. 

Nincs igazi aha élmény. Jobban belesimul a TDD elképzeléseimbe. 
Az érdekessége a ciklusok formálása és a metódusok közötti áthelyezése.
Két meghatározó lépést fedeztem fel:
  • A 6 átalakításához bevezeti a int remaining változót. A kód felbontja a hatot öt plusz egyre, így áll elő a V + I .
  • Az arabicToRoman metódus második for ciklusának és a appendRomanNumerals metódus if feltételének while ciklusra cserélése. Ekkor felismerhető, hogy gyakorlatilag ugyanazt csinálják. Így a arabicToRoman metódus while ciklusa elhagyható.

A kata leírása és a megoldás linkje ez.

Mint ígértem tegnapi élményeim...

Egy ilyen blog bejegyzés nem alkalmas a teljes háttér ismertetésére. A TDD az a teszt vezérelt fejlesztés (Test-driven development,) Ebben a WIKI bejegyzésben olvasható rövid összefoglaló.
Nagyon röviden. Először mindig a teszt osztály bővül. Ezt követi a produktív osztály bővítése, de csak annyival ami az eddigi teszteket kielégíti. Ezt a szükség szerinti re-faktorálás követi. A ciklus  ismétlődik a feladat befejezéséig.
Feladat olyan java osztály írása, amely alkalmas a gurítások bevitelére és az végeredmény kiszámítására. A feladat leírása itt olvasható, a megoldási "kulcs" innen tölthető le.
A megoldási kulcs lépésről lépésre vezet végig a javasolt úton.
Annak ellenére, hogy tudom, hogy a TDD alapelve a tesztet minimálisan kielégítő produktív kód írása, meglepő, hogy milyen jelentős strukturális módosítások lépnek be a folyamat során. Van olyan lépés, ahol szinte az egész kód cserélődik.
A kezdeti kód csak a teszt kielégítésére alkalmas, az végcélból semmit sem teljesít, köszönő viszonyban sincs a végleges állapottal.TDD nélkül már elsőre is jobb kódot írna legtöbbünk.
Az első teszt csak a produktív osztály meglétét ellenőrzi. Ezt követi a két elvárt metódus meglétének ellenőrzése. És tovább ilyen elemi lépésekben.
Érdekes tetten érni a refraktorlás érvényesítését. Kiemelésre kerül minden ami egynél többször fordul elő a kódban. A releváns lépésnek tekinthető legalább két sorból álló kódrészletek. A feltétel vizsgálatok is függvényekké válnak, nem az olvasónak kell kiértékelni mit is kíván a programozó ellenőrizni.
Eltűnnek a kommentek, mert a kód öndokumentáló.
És tényleg, mennyivel olvashatóbb a kód.
Érdemes kipróbálni!

SVN ignorálás Eclipse-ben

Címkék: Eclipse SVN Ignor

2022.11.28. 15:26

Git esetén a projekt mappájában található a .gitignor fájl az ignorálandó tételek felsorolásával. Az SVN esetén sajna nem így van, hanem svn:ignore paraméter készletbe van tárolva.
A projekt explorer fájában látható tételekre állva, ha az még nincs SVN  kontroll alatt, a Team menüben kiválasztható az Add to svn:ignor menüpont.
 
Az svn:ignore paraméter készletet szerkeszteni (hozzáadni, törölni) a projekten állva a helyi menüből a properties  lehetőséget választva a megjelenő panel bal oldalán kiválasztható az SVN info menüpont. Ekkor a panel jobboldalán az alsó táblázatos részben, ha már ignorálva lett valami, megjelenik az svn:ignor tétel. Erre duplán kattintva láthatóvá válik a szerkesztő panel. 

A JAVA fejlesztés terén egyre nagyobb szerepet kapnak a támogató eszközök. Többek között az ORM.. JAVA esetén leginkább a JPA, a Hibernate, és a Spring Data bűvös neveket lehet hallani. Persze ezek részben átfedésben vannak.

Évekkel ezelőtt tanácsot kértünk kollégánk, akkor már egy neves cégnél dolgozó gyermekétől, hogy adattárolás (perzisztencia) terén milyen irányba induljunk tovább. Felvetettük a Hibernate használatát. Azt a választ kaptuk, hogy egyes esetekben nehézkes a használata. Nehezen lehet elérni, hogy a Hibernate megértse mit szeretnénk tőle. Sajnos akkor nem tudakoltuk meg mibe is futottak bele.

Most magam is belefutottam olyan problémába, amely eszembe juttatta az esetet. Bizonyára az elmúlt idő is hozott fejlődést, de az elsőre problémás feladatot is sikerült megoldani.

A feladat a következő. JSON struktúrát kell feldolgozni. A struktúra tartalmaz egy numerikus adatok felsorolását tartalmazó nodot. Másként fogalmazva numerikus adatok tömbjét tartalmazza. Kikötés. hogy ezen lista sorrendjét meg kell tartani.

Magától értetődőnek tűnt, hogy master - details (one to many) megközelítést alkalmazzak. A master entitásban létrehoztam egy List<Long> típusú membert. Erre helyeztem el a megfelelő annotációkat.Persze a megfelelő details entitást is létrehoztam. Az autó konfiguráció kiakadt nem tudta felépíteni a konstrukciót. Nem értettem meg a probléma okát. Néztem a neten, látszólag jól csináltam. Persze nem, de erről kicsit később.

Megnéztem, volna e más megoldás. Akad az  @ElementCollection használata. Az @ElementCollection által létrehozott details tábla, alapesetben, csak a master idegenkulcsát és az értéket tartalmazza, sorrendi információt nem. Mint tudható SQL esetén a rendezetlen select sorrendje esetleges.
Két módon hidalható át a probléma. A JSON parselolását meg kell támogatni map-á alakítással, és a master struktúrában Map<Integer, Long>  membert kell használni.

@JsonDeserialize(using = ListToHashtableDeserializer.class)
@ElementCollection
@CollectionTable(name = "SERIES_DATA", joinColumns = @JoinColumn(name = "SERIES_ID"))
@MapKeyColumn(name = "DATA_ID")
@Column(name = "VALUE")
Map<Integer, Long> series;

Ehhez szükség van egy deserializer osztályra:

import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;

public class ListToHashtableDeserializer extends JsonDeserializer<LinkedHashMap<Integer, Long>> {

@Override
public LinkedHashMap<Integer, Long> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JacksonException {
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);
Iterator<JsonNode> subNodesIterator = node.elements();
LinkedHashMap<Integer, Long> result = new LinkedHashMap<>();
int index = 0;
while (subNodesIterator.hasNext()) {
JsonNode innerNode = subNodesIterator.next();
Long data = innerNode.asLong();
result.put(index, data);
index += 1;
}
return result;
}
}


Vagy a List<Long> megtartása mellett a @OrderColumn használatával. Na ez elegáns és a kívánt eredményre vezet. 

@ElementCollection
@CollectionTable(name = "SERIES_DATA", joinColumns = @JoinColumn(name = "SERIES_ID"))
@OrderColumn
List<Long> series;

 

Persze az élet nem áll meg. Kiderült, hogy az adattételekhez kapcsoltan további információ tárolására lesz szükség, ezért vissza kellett térni a one to many megoldásra.
Feltűnt, hogy az összes példa saját objektumot használ listaelemként. Így már gyanús lett és meg is találtam a kulcs mondatot. A details entitás osztálynak rendelkeznie kell paraméter nélküli konstruktorral.

A master osztályban így jelenik meg:

@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name="series_id", nullable = false)
TimeSeries timeSeries;

A JsonProperty.Access.WRITE_ONLY eredményezi, hogy ezt a tulajdonságot, csak a JSON struktúra deszérializálása során kell feldolgozni, előállítani.

A detail (child) osztályban pedig így:

...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "data_id")
private Long id;

@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name="series_id", nullable = false)
TimeSeries timeSeries;
...

Persze ehhez is kell egy deserializer osztály:

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.reg.time_series.entity.SeriesData;

public class ListToSeriesDataDeserializer extends JsonDeserializer<List<SeriesData>> {

@Override
public List<SeriesData> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JacksonException {
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);
Iterator<JsonNode> subNodesIterator = node.elements();
List<SeriesData> result = new ArrayList<>();
int index = 0;
while (subNodesIterator.hasNext()) {
JsonNode innerNode = subNodesIterator.next();
Long data = innerNode.asLong();
SeriesData SeriesData = new SeriesData(index, data);
result.add(SeriesData);

index += 1;
}
return result;
}

}

Sajnos nem jöttem rá, hogyan lehet a forrást szépen, behúzással megjeleníteni

Mostanában gyakrabban kell SQL lekérdezést gyártanom.
A feladat: a táblából szükségem lenne olyan listára amelyben minden ID-hoz csak a legfrissebb sor szerepel. Jó lenne nem használni olyan kifejezést, ami a hordozhatóságot korlátozza.

Hosszas kísérletezés a distinct, group by, join kombinációkkal. közben goolizás...
Nem igaz, hogy ez senki másnak nem kell! Végre sikerül jól feltenni a kérdést.
A https://stackoverflow.com/questions/3491329/group-by-with-maxdate oldalon olyan választ találtam ami iránymutató volt.
Íme a query:

SELECT k1.*
FROM kezeles_fej k1
LEFT JOIN kezeles_fej k2 ON (k1.vendeg_id = k2.vendeg_id AND k1.datum < k2.datum)
WHERE k2.datum IS null
GROUP BY k1.VENDEG_ID ORDER BY k1.VENDEG_ID;

Saját MAC address listázása

2017.12.02. 15:56

Szükségem volt a Win 8.1 oprendszerű gépem MAC címére.
Némi googlizás után az alábbi konzol paranccsal megszerezhető:

getmac /v /fo list

Például hiba kereséssel!

Egy régóta üzemelő Yii2 keretrendszerben fejlesztett honlap néhány hónapja átköltözött új szolgáltatóhoz. Akkor be is lőttük. A mindennapos funkciók hamar tesztelődtek. Azóta nem kellett hozzányúlni.

Most érkezett jelzés, a jegyzőkönyv feltöltés egy ideje üzenettel elutasításra  kerül.
Mire is emlékszem...
A hírekre előadásokra teljes szöveges (full-text) keresés lehetséges. Talán az utasítja el a feltöltést.
Fejlesztéskor kellet foglalkozni azzal, hogy nem minden forrásból származó pdf-et "evett meg" a parserolás+indexelés. Biztosan az üzen.
Hol is van a programban az üzenet. Ja, addig el se jut. Valójában a mime-type ellenőrzésen hasal meg. Az üzenetben ennyire részletesen nem szerepel.

A Yii2 model osztályaiban kellemesen paraméterezhető, hogy az adott tagváltozót a milyen szabály(ok) szerint kell ellenőrizni. Persze a file típusú feltöltött állomány például mime-type egyezésre:

['file', 'file', 'mimeTypes' => ['application/pdf','application/msword','application/vnd.openxmlformats-officedocument.wordprocessingml.template']]

Lehetne kiterjesztésre is, de azt ugye a csúnya, rossz hacker átírhatja.

Akkor logoljunk kint, debugoljunk itthon...
Itthon persze Windows, a hoston meg Linux. A feltöltéskor
az állomány átmeneti mappába kerül. Windows esetén kiterjesztést is kap, a Linuxon meg nem.
Na itt a kutya elásva. Ha nem áll rendelkezésre a PHP fileinfo modul, akkor a filesystemtől a nem létező kiterjesztés miatt null érték lesz a mime-type, ami nincs benne az engedélyezett listában.
A kód olyan mélyen van a hívási láncon, hogy ebbe belenyúlni nem kellene.
Akkor legyen a kiterjesztés szerinti ellenőrzés... Az meg panaszkodik, hogy kell neki a fileinfo...

Ja, tényleg úgy másfél évvel ez előtt, fejlesztés során mintha előkerült volna ez. De nem emlékszem, hogy az első telepítés során ezzel kellet volna foglalkozni. Na persze az másik szolgáltató, lehet ott alapértelmezett?
Legyen hát bekapcsolom. De jó, ez a cPanel megengedi. És tényleg működik is a feltöltés
.

Itt kap értelmet a pár sorral fentebb leírt "ha nem áll rendelkezésre a PHP fileinfo modul", Na ügye...
Próbáljuk ki újra a mime-type feltételt. Hát persze az is működik már.

Persze utólag összeáll a kép, de ehhez kellet pár óra.
Annak is vannak előnyei, ha az ember fia kopasz...

...amelyek igénylik a magyar ékezetes beállítást.
Persze csak 32 bites környezetet futtatni képes Windows esetén van értelme.

Az alábbi szerkesztéseket kell végrehajtani, kizárólag text edítor eszközzel.

A C:\Windows\System32\config.nt állomány végére bekerül az alábbi sor:
country=036,852,c:\windows\system32\keyboard.sys

A C:\Windows\System32\autoexec.nt állomány végére bekerül az alábbi sor:
C:\windows\system32\kb16 HU,852,c:\windows\system32\keyboard.sys

A gépet nem csak az alkalmazást kell újraindítani.

A "cvs history -u uuuuuuu -c -D yyy-mm-dd" parancs megjeleníti a megadott felhasználó "uuuuuuu", megadott "yyy-mm-dd" időpont után kommitálásait.
Kiegészíthető egy grep/findstr szűréssel  findstr "2017-01-12", ekkor csak az adott napi kommitokat fogjuk látni.
Persze mind ez állományba is irányítható "> d:\tmp\cvs.txt"

A teljes parancs:
cvs history -u uuuuuu -c -D 2017-01-12 | findstr "2017-01-12" > d:\tmp\cvs.txt

süti beállítások módosítása