Nyomasek Bobó automatizálva

 ( NevemTeve | 2019. január 13., vasárnap - 7:33 )

Azt mondja nekem a kódminőségellenőrző szoftver (Java programhoz), hogy a `finalize` nem jó, az üres utasításblokkokat (`{}`) pedig gond nélkül ki lehet szedni.

Namost a `finalize` ugye arra van, hogy a natív erőforrásokat (file-handle, socket, shm) valahol fel lehessen szabadítani, még akkor is, ha a hívó elmulasztotta a close/dispose/whatnot művelet meghívását (például, mert az Exception-handlerjében is volt egy-két Exception). Ha a derék szoftver az észosztás előtt belenézett volna a src.zip-be, és megnézte volna, hogy abban hol/hogyan használják a finalize-t, akkor ő is tudná.

Az üres utasításblokkot meg nem lehet elhagyni abban az esetben, amit fogott a derék BobóSw:

static void closeNX (java.io.FileInputStream is) {
    try {
      is.close ();
    } catch (Exception e) {}
}

Nem fordul a {} nélkül.

Edit: stackoverflow-ra is feldobtam, különösebb eredmény nincs egyelőre.

Szerk: Lőtt nekem még egyet: nevezzem át a publikus metódust, mert nem szép a neve. Én nincs olyan pragma, hogy @SonarFkItThisIsAlreadyInProduction, akarom mondani, ez már így marad, mint ahogy a creat sem lesz create.

Edit: egy magyar kolléga leírta megfejtést a SO-n: //NOSONAR a kérdéses sor végére

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Nem, a finalize nem erre való, Java 1.0 időkből eredő tervezési hiba, hogy létezik, és ne használd.
https://hackernoon.com/java-lang-object-finalize-is-finally-deprecated-f99df40fa71

nem meglepo hogy teve ur rosszul hasznalja ezt (aki AIX, Java, PHP, C, C++, ASM, es isten tudja miben ganyol meg _egyszerre_)

Mondjuk hobbiprogramozásnál tényleg tökmindegy, hogy felszabadul-e végül az erőforrás, vagy a világvégéig ott rohad...
Viszont ipari célú programozásnál azt nem lehet előadni a vevőnek, hogy 'naponta indítsa újra, és remélhetőleg jó lesz...'
De sőt azt sem lehet mondani, hogy évente telepítsen új JRE-t, mert esetleg az lesz a válasz, hogy eleve a programnak futásonként kb két évet kell bírnia.

"Viszont ipari célú programozásnál azt nem lehet előadni a vevőnek, hogy 'naponta indítsa újra, és remélhetőleg jó lesz...'"

Mi számít 'ipari'-nak? Egyébként meglepődnél.

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Például ahol ötévesnél fiatalabb komponenseket használnak, az garantáltan hobbifelhasználás... Az ipari felhasználó az, aki nem akar kísérleti nyuszi lenni.

FYI: van olyan rendszer, ahol az a bevett process, hogy hiba esetén újraindul a kütyü és n*10 tizedmásodpercen belül újra kell indulnia. Gondolom az autók, vonatok, miegyebek mind-mind garantáltan 'hobbifelhasználás'.

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

> Gondolom az autók, vonatok, miegyebek mind-mind garantáltan 'hobbifelhasználás'.

Itt kimaradt nekem egy logikai lépés, kérlek segíts, hogyan következik ez abból, amit írtam?

Csupán reflektálni akartam arra, hogy amit te gondolsz az 'ipari' felhasználásról, az talán kicsit távol áll a valóságtól. Ipari felhasználásba a legújabb cucctól a kőkorszakiig bármi előfordulhat.

Egyébként nem is értem ezt az 5 évet. Akkor ezek szerint egy frissen lefejlesztett szoftvernek is 5 évig a fiókban kellene porosodnia, mielőtt bárki beröffenthetné produktív környezetben.

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Hát persze, mert az azalatt az öt év alatt, amíg senki nem használja, már jól kiderültek róla a bugok és van részletes errata ;)

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

Ó én tudatlan, hiába na, csak egy hobbifejlesztő vagyok!

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Ezt mondtad: ""Viszont ipari célú programozásnál azt nem lehet előadni a vevőnek, hogy 'naponta indítsa újra, és remélhetőleg jó lesz...'""
De, ipari célú programozásnál pont az a lényeg sok helyen, hogy a rendszer túléli, ha mondjuk századmásodpercenként rebootolják. És amint van egy invalid állapot, rebootolnak és kész.

> 'naponta indítsa újra, és remélhetőleg jó lesz...'
Egyre inkabb terjed. Szomoru is vagyok emiatt, mint alkalmazas uzemelteto.

> futásonként kb két évet kell bírnia
Mutatoban lattam ilyet, nem divat manapsag (sajnos). Clustert ala es birja is a node-onkenti restartokat.
____________________
echo crash > /dev/kmem

"Egyre inkabb terjed. Szomoru is vagyok emiatt,"

Úristen, dolgozni kell, OMG! OMG! OMG!

Komolyra fordítva: ha belegondolsz, hogy a biológia és az evolúció merre mozdult el a hibák kezelését illetően, akkor igazából egész sok párhuzamot fedezhetsz fel. Ugyanis elég ciki lenne, ha nem lenne a szervezetnek valami megoldása arra, hogy hogyan kezeljen egy egyszerű vágást is vagy megoldása arra, hogy az "elhasználódott" (~="invalid állapotba került" sejteket) cserélje. Nyilván nem 1-1 az analógia, de az elég jól látható, hogy a természet mire jutott párszáz millió év alatt: hibatűrő rendszer életképesebb.

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Pontosabban a hibatűrést redundanciával kezelő rendszer életképesebb.

Az nem baj hogy dolgozni kell :) Az is nyilvanvalo hogy a hibaturo rendszer eletkepesebb.
De azt azert senki ne mondja mar hogy egeszseges dolog a hulladek kod miatt restart szakkort jatszani.
____________________
echo crash > /dev/kmem

+1

"De azt azert senki ne mondja mar hogy egeszseges dolog a hulladek kod miatt restart szakkort jatszani."

Nem, nem a hulladék kódról van szó. Hanem arról, hogy drágább dolog a hibaelhárítás szoftveresen, mint restartolni a rendszert. Mondjuk tegyük fel, hogy invalid állapotba kerül egy motorvezérlő rendszer, mert éppen szenzorhiba, kábelhiba, bármilyen más hiba miatt invalid értéket kap - például azt, hogy a fénysebesség hatszorosával megy az autó az egyik szenzorcsoport szerint.

Ekkor több időt eszik meg az, hogy kitaláld, hogyan lehet helyes állapotba helyezni a rendszert és a helyes állapotba helyezed vissza a rendszert, mint az, hogy restartolsz. Mert ugye, amíg hibaelhárítasz, addig a többi valósidejű feldolgozás nem megy, pedig mennie kéne, a rendszer meg úgy van kialakítva, hogy villámgyorsan restartol.

Már a 60-as években, az Apollo Guidance Computer úgy volt kialakítva, hogy ha egy taszk nem végez X millisecenként, vagy paritáshiba lép fel, vagy bármi invalid állapot, akkor restart.

Több idő lenne egy valósidejű feldolgozónál a hibaeset helyreállítása, mint a restart.

Jo, akkor finomitok a mondanivalomon:
De azt azert senki ne mondja mar hogy egeszseges dolog a hulladek kod miatt restart szakkort jatszani. Amikor a kodot lehetoseg lett volna jol megirni, tesztelni, idovel kijavitani, de ezek egyikere se igazan volt/van szandek.

Az vilagos hogy olykor a business jol megmondja hogy most olcsobb ujrainditgatni valamit heteken-honapokon keresztul naponta, mintsem raszanni a kod javitasra azt a kis idot. Elhiszem, kifizetik, de akkor is trogersegnek tartom. Lehet regimodi vagyok :)

Nyilvanvaloan lehet a te esetedre is peldat talalni. Az en tapasztalatom az, hogy -ugy altalaban veve- a kod egyre trogerebb (rapid vilag), es emiatt egyre tobb jarulekos problema csapodik le uzemeltetesi oldalon. De ez mar offtopic kb.
____________________
echo crash > /dev/kmem

Nem csak business software üzemeltetés van ám a világon.

Akkor ezzel az erővel minden maintenance jellegű dologra mondjuk azt, hogy hulladék.

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Azert nem :) Mas egy tervezett patcheles, reorg, ilyesmi miatt ujrainditani valamit evente 4x, es mas ujrainditani naponta 1x azert mert van pl. egy memory leak-es kodod.
____________________
echo crash > /dev/kmem

Pedig... Előző munkahelyemen határozottan emlékszem egy olyanra, hogy egyszerűen egyszerűbb volt letolni "biztos-ami-biztos" alapon inkább minden egyes objektumra egy processt minden éjjel, hiába tartott 2-3 órát annyi elemre. (Funkciót most nem részletezném). Rendszer eredetileg úgy lett tervezve, hogy esemény alapján az fut egy folyamat az eseményben érintett objektumokra. Mivel üzletileg kritikus volt inkább be lett építve még egy ilyen funkció is.

(Pontosabban először volt a napi, mert gyors volt implementálni, aztán ahogy jöttek meg szépen az eseményvezérelt futások is, úgy lett egyre kisebb jelentősége. Azonban ha valamire elmarad a process, az üzletileg rossz.)

Gazdasági _ÉS_ műszaki szempontokat figyelembe véve ez volt a leglogikusabb döntés.

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Ami neked kell a finally, vagy még inkább a try-with-resources.

Hát, vannak kétségeim, de ha akarod, mutatsd be a java.io.FileInputStream-en, hogy hogyan helyettesítenéd ezt a részt:

    /**
     * Ensures that the <code>close</code> method of this file input stream is
     * called when there are no more references to it.
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FileInputStream#close()
     */
    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            /* if fd is shared, the references in FileDescriptor
             * will ensure that finalizer is only called when
             * safe to do so. All references using the fd have
             * become unreachable. We can call close()
             */
            close();
        }
    }

Ki kell kényszeríteni, hogy a program ne tudja véletlenül nyitvahagyni a fájlt. "Ilyen egyszerű."

Igazából a finalizer olyasmi mint a biztonsági háló a légakrobatikában: ha a használatára sor kerül, akkor már baj van. Ezzel együtt megértem, hogy vannak akik nem akarnak nélküle dolgozni...

+1

Az üres utasításblokkba csak be kell írni egy NOP-ot és megkommentezni, hogy ezért van ott :-)

A finalize-ra nem nagyon van jó megoldás. Mármint ha nincs a programban leak és mindent szépen lezárogat időben "kézzel" (lásd try finally AutoCloseable minta), akkor az jó. De ha valami lib elfelejt néhányat lezárni, és elkezdenek ezek a heapen kallódni az már nem jó.

De az ilyen helyzet még menthető volna például kézzel periódikusan triggerelt GC-vel. Példaul ha lefuttatom "kézzel" a GC-t pl óránként! Nem, nem, nem! A GC-t nem futtathatod kézzel, majd a VM eldönti, hogy mikor jó neki!

Van System.gc hívás, de nincs rá garancia, hogy valóban le is fut! Például ha csak egyszer hívod meg, akkor többnyire nem lesz teljes GC! Tehát a finalizálandó objektumod megúszhatja ha valami öregebb generációban csücsül.

Igazából a JVM memória és egyéb resource foglalását elég nehéz kézbentartani. A "kedvencem" a natív memóriát foglaló (direkt) ByteBuffer ojjektum. Ennek szabályos API-ja nincs is a tárterület felszabadítására, csak Unsafe-en keresztül lehet elérni, hogy szabadítsa fel a memóriát. Egyébként lényegében finalize-ban van csak felszabadítva. Ebből az következik, hogy ha nem csinálod meg kézzel a finalize-t az összes ilyen ojjektumodra, akkor nem tudsz egy pontos képet se kapni arról, hogy éppen mennyi memóriád van szabad, mert az csak GC után derülne ki, azt meg nem tudod kikényszeríteni.

Jó nagy káosz ez a GC, kár hogy nem nagyon van jobb.

Ami nagyon jó a GC-ben, hogy memória töredezés viszont nincsen benne. Ugye amiben van memória töredezés, abban nagyon nehéz olyat állítani, hogy ez a program márpedig működni fog két évig megállás nélkül. Lényegében csak menedzselt memóriájú nyelvvel, illetve teljesen statikusan foglalós C-vel lehet ezt könnyű szívvel igazoltan kijelenteni.

2 éves futásidő már nem rossz egy processztől, gratulálok hozzá!

Az üres utasításblokkba csak be kell írni egy NOP-ot és megkommentezni, hogy ezért van ott :-)

A SonarQube azt sem fogadja el. :-)

Csak Java játszik?

Igazából SonarQube a termék neve, és más programnyelvekhez is ért.

Na a SonarCube-ról megvan a véleményem...

Van egy cégünk által karbantartott szoftver, JavaEE5-re írt cucc, párezer class-al. 0-24-es szoftver, komoly helyen használják. Irtózatosan szarul van megírva, én állatorvosi lónak hívom, ugyanis bármilyen programozási vagy architekturális típushibát mondasz, mutatok a szoftverben példát rá. Go-t programozó kollégánk vicces akart lenni, és mondta h mutassak neki "resource use after free" típusút (JavaEE környezet), de tudtam neki, sőt, a szoftver egyik kulcsfontosságú részén van a hiba. A kijavítása kockázat, annyi workaround van körülötte.

Na erre a szoftverre a SonarCube "A" osztályztatot adott, és összeszarta magát, mikor dependency matrixot kértünk (amit az IDEA viszont megcsinált szépen).

Ha már senki nem írja le, a helyes megoldás az ilyen, cleanupot igénylő dolgokra (ha a kódbázisban nem tudod másként kikényszeríteni) a PhantomReference és a ReferenceQueue használata.
Ekkor értesítést kapsz róla, hogy melyik objektum vált a GC szerint felszabadíthatóvá és fel tudod szabadítani a hozzá tartozó erőforrásokat.

https://www.ibm.com/developerworks/library/j-refs/

A finalize() használata pedig hatalmas code smell, aki finalize-ot ír le, nem ismeri igazából a Java platformot.

(Nem nézted át a src.zip-ben a finalize előfordulásait, ugye? Nem baj, semmi bajod nem lesz belőle.)

Te meg nem tudod, hogy a runtime implementációja, meg a runtimeon futó szoftver két külön dolog, de sebaj, nem lesz belőle bajod.
A te esetedben (FileInputStream) ez a finalize():

/**
     * Ensures that the {@link #close} method of this file input stream is
     * called when there are no more references to it.
     * The {@link #finalize} method does not call {@link #close} directly.
     *
     * @apiNote
     * To release resources used by this stream {@link #close} should be called
     * directly or by try-with-resources.
     *
     * @implSpec
     * If this FileInputStream has been subclassed and the {@link #close}
     * method has been overridden, the {@link #close} method will be
     * called when the FileInputStream is unreachable.
     * Otherwise, it is implementation specific how the resource cleanup described in
     * {@link #close} is performed.
     *
     * @deprecated The {@code finalize} method has been deprecated and will be removed.
     *     Subclasses that override {@code finalize} in order to perform cleanup
     *     should be modified to use alternative cleanup mechanisms and
     *     to remove the overriding {@code finalize} method.
     *     When overriding the {@code finalize} method, its implementation must explicitly
     *     ensure that {@code super.finalize()} is invoked as described in {@link Object#finalize}.
     *     See the specification for {@link Object#finalize()} for further
     *     information about migration options.
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FileInputStream#close()
     */
    @Deprecated(since="9", forRemoval = true)
    protected void finalize() throws IOException {
    }

Ennyit a finalize-ról FileInputStream esetén. Az implementációja üres, nem hívja meg a close()-t, amit expliciten meg kell hívni, vagy try-with-resource-zel használni.
De hát ez le is van írva az API doksiban.
https://docs.oracle.com/javase/9/docs/api/java/io/FileInputStream.html#finalize--

Menjünk vissza a logikához:

1. állítás: A finalize() használata pedig hatalmas code smell, aki finalize-ot ír le, nem ismeri igazából a Java platformot.

2. állítás: A Java source-ben sok finalize van.

Következtetés: A Java source-t olyanok készítették, akik nem ismerik igazából a Java platformot.

Szerk: idézet a Java8 java.io.FileInputStream-ből:

    /**
     * Ensures that the <code>close</code> method of this file input stream is
     * called when there are no more references to it.
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FileInputStream#close()
     */
    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            /* if fd is shared, the references in FileDescriptor
             * will ensure that finalizer is only called when
             * safe to do so. All references using the fd have
             * become unreachable. We can call close()
             */
            close();
        }
    }

Java10:

        @Override
        @SuppressWarnings("deprecation")
        protected final void finalize() {
            try {
                if ((fis.fd != null) && (fis.fd != FileDescriptor.in)) {
                    /* if fd is shared, the references in FileDescriptor
                     * will ensure that finalizer is only called when
                     * safe to do so. All references using the fd have
                     * become unreachable. We can call close()
                     */
                    fis.close();
                }
            } catch (IOException ioe) {
                // ignore
            }
        }

a java src.zip tele van `sun.misc.Unsafe` hívásokkal is (meg még ki tudja mivel), de ettől függetlenül ha ilyet írsz, mindenki csúnyán néz rád.

másrészt jónéhány éve mindenhol elhangzik a core jdk fejlesztők részéről is, hogy:

- ne
- ne
- ne akarj `finalize`-t írni
- tényleg ne
- ha ilyen funckióra van igényed, van kismillió egyéb lehetőséged
- ne
- ki fogjuk vezetni
- szerintünk is hiba volt anno
- ne

ez nem mond ellent annak, hogy ~10+ éves kódbázisban még elő-elő fordul.

Ez is jó dolog, de azért kellene indoklás is, azon túl, hogy 'a memory-only erőforrások amúgy is felszabadulnak'. Például egy Windows!HWND az nem memory-only erőforrás, mégis jó lenne egy biztonsági eszköz, hogy előbb-utóbb mégis felszabaduljon.

Ez a biztonsági eszköz Java 1.2 óta rendelkezésre áll, PhantomReference és ReferenceQueue a neve a class párosnak, ami megvalósítja, ha te szeretnéd csinálni try-with-resources nélkül.
Ha meg try-with-resources-zel használható, akkor az az osztály, aki tartalmaz HWND referenciát, az implementálja az AutoClosable interfészt és kész, használható try-with-resources-zel.

van indoklás. én kettőt tudok neked mondani, ami így megmaradt az évek alatt bennem (úgy, hogy életemben egy finalize-t nem írtam soha egyetem után, mert nem kell):

- erőforrásigényes, mert a finalizenak a GC egy jól meghatározott pontján le kell futnia, míg a refQueue-t majd később feldolgozhatod
- inkonzisztens, mert a finalizeban visszarakhatod elérhetővé az objektumot

ettől függetlenül van megoldás arra, hogy ezeket az erőforrásokat felszabadítsd, pl. lásd a szálnyitót.

Sőt, ha finalize-ban kivételt dobsz, az egyéb problémákat is okozhat.

igen, ez is eszembe jutott, hogy az is nagyonjó interjúkérdés, de arra még a választ se tudom (meg nem is annyira akarom tudni), így végül nem írtam bele.

A JLS így specifikálja:
"If an uncaught exception is thrown during the finalization, the exception is ignored and finalization of that object terminates. "
Ez azért baj, mert így aztán nem tudja elkapni senki, plusz az objektum félig felszabadított állapotban marad, ami gáz.

Nem tudok szabadulni a gondolattol, hogy te tulajdonkeppen Javaban akarsz C++ kodot irni, es ezert nem ertjuk / nincs ertelme annak, amit konkretan csinalsz (sajnos a kontextust nem irtad, igy nehez rajonni, tulajdonkeppen mit is akarsz elerni ezzel az egesszel).

Igazi Programozó minden nyelven tud FORTRAN programot írni.

Nem akarom többször leírni, kérlek olvass vissza. Vagy nézd meg itt külföldi nyelven.

??? Semmit nem tudunk a problemarol, amit meg akarsz oldani (= mit kellene, hogy csinaljon a kod?).

Az, hogy a SonarQube-nel "okosabb vagy", csak egy tunet, nem a problemak forrasa.

Szomorúan hallom. Nyilván láthatatlanul írtam.

Pedig érdemes lenne megfogadni a többiek tanácsát és dobni a 'finalize'-t.

Kb. arról szól a filozofálás, hogy szabad-e új program írásába kezdeni MSDOS alatt Turbo Pascal nyelven? A finalize legacy cucc, eljárt felette az idő.

Természetesen mindent szabad, de ha nem akarod szanaszét szívatni magad, akkor használj rendes programnyelvet, rendes operációs rendszer mellett.

A legtöbb ellenőrző eszköznek meg lehet adni spéci kommenteket, hogy "igen, tudom mit csinálok, finalize-t akarok".
Ebben az esetben nem fog tovább csesztetni.

Amikor web alkalmazást írtam, a bináris ÉS,VAGY műveletekért elkezdett egy ellenőrző eszköz bizgetni. A legtöbb esetben a javascript-nek nincs szüksége |, &, ^ műveletekre. Kizárólag azokban a fájlokban, ahol ilyen műveletek kellettek, kikapcsoltam a tiltást. Ennyire egyszerű.

Mondjuk a JS-ben egyféle számtípus van, IEEE 754 lebegőpontos, ott tényleg nincs sok értelme a bitműveleteknek. Nem is értem, mit akartál vele rendesen csinálni. A folyamatos int és double közötti kasztolgatással egy csomó CPU-t pazarolsz el, ahelyett, hogy nem bitflageket használnál.

Intel hex fájlt konvertálni binárissá.

A logokat meg a mikrovezérlő binárisan küldi, base64-be alakítva.

2k RAM mellett egy AVR azért módjával loggol. A teljes láncban a legerősebb kütyü a böngésző.

Mármint van egy Base64 stringed, amit át akarsz alakítani JS segítségével bináris adattá?
Van rá API. https://html.spec.whatwg.org/multipage/webappapis.html#dom-atob
Gyakorlatilag minden támogatja.
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/atob

Bináris adat jön base64-ként.

> a PhantomReference és a ReferenceQueue használata.

Köszönöm, megnézem.

Ha van lehetőséged Java 9-et vagy újabbat használni, akkor rendelkezésre áll egy még magasabb szintű API, gyakorlatilag megírták azt, amit amúgy magad is megcsinálnál PhantomReference és ReferenceQueue segítségével:
https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html
Csak meg kell adnod, hogy melyik objektum felszabadítását kell figyelni, és mi az a Runnable, amit végre kell hajtani, miután az objektum a GC szerint phantom reachable.