Nyomasek Bobó automatizálva

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ások

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™

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™

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™

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.

Ezt csak most láttam meg....
A reboot-olás és a túlélés két ellenkező dolog; ráadásul itt pont Jáváról beszélünk, ahol egy nemtriviális alkalmazás indulási ideje tipikusan 100-500 századmásodperc. (Most gondoljunk bele, hogy mennyit halad előre ennyi idő alatt a lézervágó vagy az önvezető autó.)

És az mennyire jó, ha invalid állapotban van az önvezető autó? Pont arról beszélgetünk, hogy van olyan helyzet, ahol az invalid állapot felismerése, kezelése, valid állapotba való visszatérés több ideig tart, mint a restart - ilyenkor tök normális, ha restartolnak.

Te mit tennél akkor, ha 0.1 másodperc a restart, és 0.2 másodperc a hiba helyreállítás? mikor lesz szarabb a lézervágónak, vagy az önvezető autónak?

> '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™

"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

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...

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á!

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.

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#…--

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 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.

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.

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.

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.