Jekyll2023-09-28T20:36:27+02:00/feed.xmlPetrik segédletekSegédletek különféle programozási, szoftverfejlesztési témakörökhöz. Eredetileg a BMSZC Petrik 13-14 évfolyamos, OKJ-s szoftverfejlesztői tantervéhez készült (ami azóta többször átalakult). Bárki szabadon felhasználhatja a CC-BY-NC 4.0 szerint (részletek az Infó oldalon).Titkosítás, biztonság2020-03-29T11:00:00+02:002020-03-29T11:00:00+02:00/2020/03/29/titkositas<p>Amikor titkosításról és biztonságról beszélünk, meg kell különböztetnünk a különféle eseteket, amelyek ellen védekezni szeretnénk, pl.:</p>
<ul>
<li>Véletlen, nem szándékos meghibásodás ellen</li>
<li>Szándékos rongálás ellen</li>
<li>Hamisítás ellen</li>
<li>Illetéktelen hozzáféréssel szemben</li>
<li>stb.</li>
</ul>
<p>Az esettől függően különféle eszközök állnak a rendelkezésünkre.</p>
<p>A történetünknek legyen három szereplője:</p>
<ul>
<li>Alice: szeretne egy titkos üzenetet küldeni Bobnak</li>
<li>Bob: az üzenet fogadója</li>
<li>Eve: szeretné az üzenetet útközben megszerezni, elolvasni, és ha tudja, akár meg is hamisítani.</li>
</ul>
<h2 id="a-probléma">A probléma</h2>
<p>Alice és Bob között a kapcsolat sajnos nem megbízható. Nem tudhatjuk, hogy épp ki hallgatja a telefonbeszélgetésüket, nem néznek-e bele az email-ekbe, a chat-ekbe.</p>
<p>Alice-nak és Bob-nak csak a saját eszközeikre (telefon, számítógép) van ráhatása.</p>
<ul>
<li>Telefonos beszélgetés:
<ul>
<li>A rádióhullámokat bárki befoghatja</li>
<li>Az adótornyokat a telefon-szolgáltató üzemelteti, ott összegyűjthetnek minden beszélgetést/SMS-t</li>
</ul>
</li>
<li>Internetes chat:
<ul>
<li>Wifi-t befoghatja mindenki a közelben</li>
<li>Az internetszolgáltató lát minden elküldött adatcsomagot</li>
<li>Városok, országok közti kommunikáció esetében több szolgáltatón is keresztül megy minden adat</li>
</ul>
</li>
<li>Posta
<ul>
<li>A postások bármelyik levélet kibonthatják</li>
<li>A helyi postán minden helyi levél átfut</li>
</ul>
</li>
</ul>
<p>Ha csak egy láncszem is gyenge a fentiek közül, akkor Eve könnyedén meg tudja szerezni az üzenetet, pl. a postás lefizetésével.</p>
<p>Amikor biztonságról beszélünk, akkor két alapvető kritériumot kell teljesítenünk:</p>
<ul>
<li><strong>A rendszernek akkor is biztonságosnak kell maradnia, ha az alatta élvő kommunikációs csatorna nem az</strong>: a postás minden levelet elolvas, a szolgáltató megvizsgál minden adatcsomagot stb.</li>
<li><strong>A rendszernek akkor is biztonságosnak kell maradnia, ha a támadó ismeri titkosítási algoritmust</strong>: a <a href="https://hu.wikipedia.org/wiki/Bizonytalans%C3%A1gon_alapul%C3%B3_biztons%C3%A1g">titkolózáson alapuló biztonság</a> (security through obscurity) valójában nem biztonság, csak annak a látszatát kelti.</li>
</ul>
<h2 id="hagyományos-egykulcsosszimmetrikus-jelszavas-titkosítás">Hagyományos egykulcsos/szimmetrikus (jelszavas) titkosítás</h2>
<p>Az ilyen jellegű titkosítási algoritmusokat azért nevezzük szimmetrikusnak, mert a titkosítási kulcs kell az üzenet visszafejtéséhez.</p>
<p>Az egyik legrégebb óta ismert titkosítási forma az ún. <a href="https://hu.wikipedia.org/wiki/Caesar-rejtjel">Caesar-kód</a>, amelyben az üzenet betűit N darab karakterrel odébb toljuk (Z esetén az A betűtől indulunk újra).</p>
<p>Pl. ha N=3</p>
<pre>
Eredeti: HELLO
Titkosított: KHOOR
</pre>
<p>Ebben az esetben, még ha Eve útközben megkaparintja a levelet, abban annyi fog szerepelni, hogy KHOOR, amely nem értelmes. Viszont ha Bob kézhez kapja a levelet, ő tudni fogja, hogy a betűk 3-mal el lettek tolva. Ha a műveletet elvégzi visszafelé (az ABC másik irányába tolja el a betűket 3-mal), akkor visszakapja az eredeti üzenetet.</p>
<p>Ez a konkrét algoritmus az ókorban még működhetett, ma már azoban az ilyen egyszerű, behelyettesítésen alapuló algoritmusokat nagyon könnyű visszafejteni. A ma használt szimmetrikus algoritmusok ennél összetettebbek, hogy a visszafejtést megelőzzék.</p>
<p>A szimmetrikus algoritmusok gyengéje, hogy a titkos kulcsot először valahogyan el kell juttatni Bobhoz. Ha Bob nem tudja, hogy 3-mal kell a betűket eltolni, ő sem fogja tudni elolvasni az üzenetet. Ha a titkos kulcsot Alice elküldi Bob-nak, azt Eve lehallgathatja, ami után már ő is el tudja olvasni az üzeneteket.</p>
<p>A leggyakrabban használt és ajánlott algoritmus: AES</p>
<p>Fontos! A szoftverfejlesztői OKJ tételek közt szerepel a DES/3DES algoritmus. Ezek mai szemmel már gyengék, elavultnak számítanak, új alkalmazásban ne használjuk őket!</p>
<h2 id="kétkulcsosaszimmetrikus-titkosítás">Kétkulcsos/aszimmetrikus titkosítás</h2>
<p>Tegyük fel, hogy Alice és Bob nem tudnak egy titkos kódban megegyezni előre, mégis szeretnének üzenetet váltani, lehallgatás nélkül.</p>
<p>Először is nézzünk egy kis gondolkodtató, találós kérdést. Alice-nak van egy doboza, amire rátehet egy lakatot. Ekkor nem tud más belenézni. De ha Bob megkapja a lezárt dobozt, ő sem tudja kinyitni.</p>
<p>Mi a megoldás?</p>
<p><br />
<br />
<br />
<br />
<br />
<br />
<br /></p>
<p>Segítség: az alfejezet címe.</p>
<p><br />
<br />
<br />
<br />
<br />
<br />
<br /></p>
<ul>
<li>Alice lezárja a dobozt, és elküldi Bobnak</li>
<li>Bob megkapja a lezárt doboz, amit nem tud kinyitni, ezért <em>ő is rátesz egy lakatot</em>, majd visszaküldi Alice-nak
<ul>
<li>Most útközben már két lakat is van rajta</li>
</ul>
</li>
<li>Alice leveszi a saját lakatját. Ő már nem tudja kinyitni, mert Bob lakatja rajta van</li>
<li>Ismét elküldi Bobnak, aki most már a saját kulcsával ki tudja nyitni a dobozt</li>
</ul>
<p>A módszer lényege, hogy egy időpillanatban sem volt olyan, hogy a doboz lakat nélkül utazott volna, és a kulcs sem hagyta el a tulajdonosait.</p>
<p>Eve persze használhatott volna erőt is, pl. levágja magát a lakatot, vagy darabokra szedi magát a dobozt - a gyakorlatban nem lesz használható. Ettől függetlenül ez a találós kérdés rávilágít arra, hogy nem muszáj megosztani a kulcsokat/jelszavakat a titkosításhoz, létezhet megoldás.</p>
<h3 id="az-üzenet-titkosítása">Az üzenet titkosítása</h3>
<p>Az algoritmus nem egy titkosítási kulcsot használ, hanem egy ún. összetartozó <em>kulcspárt</em>:</p>
<ul>
<li>K1</li>
<li>K2</li>
</ul>
<p>Amit K1-gyel titkosítottunk, azt csak K2-vel lehet visszafejteni, és viszont: amit K2-vel titkosítottunk, azt csak K1-gyel lehet visszafejteni:</p>
<p>Eredeti üzenet → K1 → <em>titkos</em> → K2 → Eredeti üzenet</p>
<p>vagy</p>
<p>Eredeti üzenet → K2 → <em>titkos</em> → K1 → Eredeti üzenet</p>
<p>Ezt a tulajdonságát felhasználhatjuk a fenti probléma megoldására:</p>
<ul>
<li>Nevezzük ki K1-et <strong>publikus kulcs</strong>nak, és osszuk meg mindenkivel</li>
<li>Legyen K2 <strong>privát kulcs</strong>, és tartsuk titokban, magunknál.</li>
</ul>
<p>Ennek két haszna is lesz:</p>
<h3 id="publikus-kulcsú-titkosítás">Publikus kulcsú titkosítás</h3>
<p>Az alaphelyzet megint ugyanez: Alice megint üzenetet akar küldeni Bobnak, de most az interneten keresztül, így nincs fizikai doboz, amibe elrejthetné az üzenetet.</p>
<p>Viszont ismeri Bob publikus kulcsát.</p>
<ul>
<li>Alice titkosítja az üzenetét Bob publikus kulcsával</li>
<li>Eve el tudja olvasni a titkosított üzenetet, de mivel nem tudja Bob privát kulcsát, nem tudja értelmezni</li>
<li>Bob vissza tudja fejteni a saját kulcsával</li>
</ul>
<h3 id="digitális-aláírás">Digitális aláírás</h3>
<p>Mivel Eve-nek most már nincs lehetősége a leveleket elolvasni, más támadási formák után néz. Az alábbi üzenetet írja:</p>
<blockquote>
<p>Kedves Bob!</p>
<p>Tudod, milyen jó barátok vagyunk, és segítünk egymásnak a bajban.
Sajnos anyagilag megszorultam egy kissé. Ki tudnál segíteni egy kis összeggel?
Erre a számlaszámra küldd légyszi: XXX-YYY-ZZZ</p>
<p>Köszi,<br />
Alice</p>
</blockquote>
<p>Bob honnan fogja tudni, hogy az üzenetet tényleg Alice küldte?</p>
<p>A kétkulcsú titkosítás ezt is meg tudja oldani.</p>
<p>Ha Alice egy hiteles üzenetet szeretne küldeni, akkor először titkosítja a <em>saját privát kulcsával</em>. De ennek mi értelme? A publikus kulcsot mindenki ismeri, bárki elolvashatja az üzenetet, nem?</p>
<p>A cél nem is ez. Ha valakinek sikerült visszafejteni az üzenetet Alice publikus kulcsával, akkor az üzenet csak tőle származhat: mert más nem tudta volna így titkosítani. Épp ezért ezt nem is titkosításnak szokták nevezni, hanem <strong>digitális aláírás</strong>nak.</p>
<p>Mivel Eve nem ismeri Alice privát kulcsát, ezért nem tud az ő nevében üzenetet küldeni.</p>
<h3 id="a-kettő-együtt">A kettő együtt</h3>
<p>Tehát ha Alice szeretne egy üzenet küldeni Bobnak, amit Eve se elolvasni, se meghamisítani nem tud, az alábbiakat kell tennie:</p>
<p><em>Eredeti üzenet</em> → Alice privát kulcsa → <em>Aláírt üzenet</em> → Bob publikus kulcsa → <em>Aláírt és titkosított üzenet</em></p>
<p>Bob pedig ezeket a lépéseket fogja végrehajtani:</p>
<p><em>Aláírt és titkosított üzenet</em> → Bob privát kulcsa → <em>Aláírt üzenet</em></p>
<blockquote>
<p>Szóval az üzenet nekem szólt, más nem olvashatta el!</p>
</blockquote>
<p><em>Aláírt üzenet</em> → Alice publikus kulcsa → <em>Eredeti üzenet</em></p>
<blockquote>
<p>Szóval ez tényleg Alice-tól jött!</p>
</blockquote>
<p>A lépéseket természetesen szoftver végzi, így történik a kommunikáció pl. a webszerver és a böngésző között (HTTPS), de pl. <a href="https://hu.wikipedia.org/wiki/PGP">PGP</a> segítségével tetszőleges dokumentumra, emailre is alkalmazható.</p>
<h3 id="rsa-dsa">RSA, DSA</h3>
<p>A két leggyakrabban használt algoritmus. A böngészőben, a lakat ikonra kattintva megnézhetjük, hogy ez az oldal is RSA titkosítást használ.</p>
<p>Saját kulcspárt generálhatunk pl. OpenSSH segítségével. Ha szeretnénk egy 4096 bites, RSA kulcsot:</p>
<pre><code class="bash">ssh-keygen -t rsa -b 4096
</code></pre>
<p>Ha alapértelmezett fájlnéven hagyjuk, akkor két fájlt generál a “~/.ssh” mappában:</p>
<ul>
<li>id_rsa: ez a privát kulcs</li>
<li>id_rsa.pub: ez a publikus kulcs</li>
</ul>
<p>Windows alatt egy grafikus alkalmazás a <a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">PuTTY</a> ssh klienshez mellékelt puttygen.exe, amivel szintén lehet kulcspárokat generálni.</p>
<h2 id="egyirányú-titkosítás">Egyirányú titkosítás</h2>
<p>Bizonyos szituációkban nem fontos, hogy a titkosítás visszafordítható legyen. Ilyen eset pl. a jelszavak tárolása. Ha van módszerünk a jelszavak egyezésének az ellenőrzésére, akkor a visszafejtésre nem is kell, hogy létezzen módszer. Sőt, ez biztonsági hibának számítana!</p>
<p>Azokat az algoritmusokat, amik erre alkalmasak, <strong>kriptográfiai hash</strong> függvényeknek nevezzük.</p>
<p><a href="/2018/12/01/hash.html">Emlékeztetőül, a hash függvény:</a></p>
<ul>
<li>Egyirányú, a hash-ből nem lehet visszaszerezni az eredeti adatot</li>
<li>Fix intervallumra vetíti le az eredeti adatot, így nem derül ki, hogy az eredeti 1 bájt vagy 1 gigagbájt volt: a bcrypt eredménye mindig 184 bites lesz.</li>
</ul>
<p>Nem minden hash függvény alkalmas jelszavak tárolására. A CRC32-t például véletlen hibák ellen találták ki, nagyon egyszerű kulcsütközést generálni.</p>
<p>A jelszó helyességének az ellenőrzéséhez az adatbázisban eltárolt jelszót nem kell visszafejteni, hanem elég a felhasználó begépelt jelszavát hash-elni. Ha a most kiszámolt, és az adatbázisban tárolt hash megegyezik, akkor a user eltalálta a jelszót.</p>
<p>Ha valaki pedig megszerezné az adatbázisban tárolt hash-eket, akkor nem menne velük sokra: a hash-ekből nem lehet visszaszerezni az eredeti jelszót.</p>
<h3 id="salt">Salt</h3>
<p>A jelszót titkosítás előtt meg is “sózhatjuk”, ezt azt jelenti, hogy egy kis méretű, véletlenszerű adatot hozzáfűzünk. Ennek az a célja, hogy ha a rendszerben két user véletlenül ugyanazt a jelszót választja, ez ne derüljön ki a hash-ek egyezéséből.</p>
<p>Salt nélkül:</p>
<ul>
<li>User1:
<ul>
<li>hash(jelszo1) = asdf1234</li>
</ul>
</li>
<li>User2:
<ul>
<li>hash(jelszo1) = asdf1234</li>
</ul>
</li>
</ul>
<p>Viszont ha a jelszavakat megsózzuk:</p>
<ul>
<li>User1
<ul>
<li>salt = ab</li>
<li>hash(jelszo1ab) = a1223445</li>
</ul>
</li>
<li>User2
<ul>
<li>salt = cd</li>
<li>hash(jelszo1cd) = 89as5zb5</li>
</ul>
</li>
</ul>
<p>Ugyanaz a jelszó, de a hash különbözik.</p>
<h3 id="php-példakód">PHP példakód</h3>
<p>Regisztrációkor:</p>
<pre><code class="php">// Hash kiszámítása, ezt fogjuk az adatbázisba tenni
$db_hash = password_hash($_POST['password'], PASSWORD_DEFAULT);
</code></pre>
<p>Bejelentkezéskor:</p>
<pre><code class="php">$db_hash = ... // Ezt olvassuk ki az adatbázisból
if (password_verify($_POST['password'], $db_hash)) {
// Sikeres login
$_SESSION['user_id'] = $user_id;
} else {
$error_message = 'Sikertelen login';
}
</code></pre>
<p>Javasolt kriptográfiai hash algoritmusok:</p>
<ul>
<li>bcrypt (PHP alapértelmezett)</li>
<li>Argon2 (PHP beépített):
<ul>
<li>password_hash($pw, PASSWORD_ARGON2I);</li>
</ul>
</li>
<li>PBKDF2 + SHA-512</li>
</ul>
<p><strong>Ellenjavalt algoritmusok:</strong></p>
<ul>
<li>MD5, SHA1</li>
<li>bármilyen algoritmus, amit nem lehet paraméterezni az iterációk számával</li>
</ul>Amikor titkosításról és biztonságról beszélünk, meg kell különböztetnünk a különféle eseteket, amelyek ellen védekezni szeretnénk, pl.:Szoftverfejlesztési módszertanok2019-03-07T10:00:00+01:002019-03-07T10:00:00+01:00/2019/03/07/modszertanok<p>Egy szoftver fejlesztésének az életciklusa 4 fő lépésből áll:</p>
<ul>
<li>Követelmények összegyűjtése, specifikáció</li>
<li>Tervezés</li>
<li>Fejlesztés, megvalósítás</li>
<li>Tesztelés</li>
<li><em>+1: Karbantartás (az első átadás/kiadás után folyamatos)</em></li>
</ul>
<p>Attól függően, hogy ez a négy elem hogyan kapcsolódik egymáshoz, különféle módszertanokat különböztetünk meg.</p>
<p>A gyakorlatban ahány cég/csapat, annyiféle módszer létezik, ezért most csak legfontosabbakkal, a főbb kategóriákkal ismerkedünk meg.</p>
<h2 id="vízesés">Vízesés</h2>
<p>A fázisok elkülönülnek egymástól, egymásra épülnek:</p>
<p><img src="/assets/img/modszertan-vizeses.svg" alt="Vízeses modell" /></p>
<p>A naiv elgondolás, hogy ez a módszer a logikus, hiszen hogyan is lehetne elkezdeni a tervezést a specifikáció nélkül, hogyan lehetne tesztelni kész szoftver nélkül stb.</p>
<p>A módszer legnagyobb problémája, hogy arra a feltételezésre épül, hogy az összes megelőző lépés helyes, hibátlan volt. A gyakorlatban azonban sokszor fordul elő, hogy a fejlesztés során találunk tervezési hibákat, sőt, akár specifikációbeli változásokat is. Visszatérni egy előző fázisba viszont azt jelenti, hogy az addig elvégzett munkát ki kell dobni.</p>
<p>Épp ezért, tisztán vízesés jelleggel csak nagyon kevés szoftvert fejlesztenek, főleg olyan területeken, ahol termék elkészülte után nincs lehetőség, vagy nagyon költséges a frissítés. Pl. egy műhold vezérlőszoftverét nem lehet iteratív módon fejleszteni, elsőre jól kell működnie - ez viszont azt is magával vonzza, hogy a specifikációs és a tervezési fázis <em>sokkal</em> több ideig tart és sokkal költségesebb, mint egy átlagos szoftverprojektté.</p>
<h2 id="iteratív">Iteratív</h2>
<p>A legtöbb szoftver valamilyen iteratív módszerrel készül, vagyis nem egyszerre készül el a végleges szoftver, hanem kisebb, önállóan is használható egységekre kell bontani.</p>
<p><img src="/assets/img/modszertan-iterativ.svg" alt="Iteratív modell" /></p>
<p>Minden iteráció ugyanebből a négy elemből áll, de az időtartamuk rövidebb. Ezért ha pl. a megváltozott specifikáció vagy hibás tervezés miatt újra kell kezdeni a fejlesztést, akkor sokkal kevesebb időt/pénzt pazaroltunk.</p>
<p>Egy iteráció időben változó lehet, pár héttől pár hónapig is terjedhet a szoftvertől függően. Emellett, okos projektmendzsmenttel és verziókezeléssel az egyes funkciókat egymással párhuzamosan, ennél kisebb egységekben is lehet fejleszteni:</p>
<ul>
<li>A Windows 10-ből átlagosan fél évente jelenik meg új verzió
<ul>
<li>Ez nem jelenti azt, hogy a fejlesztés egy nagy iterációban történik, hanem sok kisebb iteráció segítségével készülnek az egyes funkciók
<ul>
<li>Amelyeknek van saját tervezési, fejlesztési, tesztelési fázisa</li>
</ul>
</li>
<li>A kiadás előtt pedig történik egy nagyobb méretű, átfogó tesztelés</li>
</ul>
</li>
</ul>
<h3 id="agilis-módszerek">Agilis módszerek</h3>
<p>Az agilis módszertan az iteratív módszerek extrém példája, egy iteráció legfeljebb 2-3 hétből áll. Azt helyezi előtérbe, hogy sok esetben a követelmények <em>folyamatosan változnak</em>, és a jövőre nem lehet felkészülni. Ezért a cél, hogy egy működőképes szoftver minél hamarabb készüljön el, és a megrendelői / felhasználói visszajelzések segítsék a további tervezést.</p>
<p>Az egyik legnépszerűbb használati területe a weboldalak és app-ok készítése.</p>
<p>Egy népszerű, agilis módszertan a <a href="https://en.wikipedia.org/wiki/Scrum_(software_development)">Scrum</a>. (megj.: ebben az esetben a magyar nyelvű wikipédia szócikket NEM ajánlom).</p>
<p>Agilis módszerek is vannak limitációi, pl. nem alkalmaztóak olyan helyzetekben:</p>
<ul>
<li>ahol a fejlesztésre, tesztelésre szigorú előírások vonatkoznak, mint pl. orvosi eszközök készítése, vagy az autóipar (maga a tesztelés, engedélyeztetés több idő, mint egy iteráció)</li>
<li>ha a projeknek sok külső függősége van, a csapatoknak egymásra / az ügyfélre kell várniuk</li>
</ul>Egy szoftver fejlesztésének az életciklusa 4 fő lépésből áll:Nyílt forráskód, szabad szoftver2019-03-06T11:00:00+01:002019-03-06T11:00:00+01:00/2019/03/06/szabad-szoftver<p>Nyílt forráskódú szoftvernek azt nevezzük, amikor a szoftver forráskódja a fejlesztők számára szabadon elérhető, és az módosítható, akár saját programba be is építhető. Köznyelvben a nyílt forráskódú (open-source) és a szabad (free/libre) szoftver fogalma rokon értelmű.</p>
<p>Attól, hogy a forráskód elérhető, még nem lesz a szoftver nyílt: a Windows operációs rendszer forráskódja bizonyos célokra (pl. hardverfejlesztés) bizonyos feltételek mellett (pl. több ezer fős cég, titoktartási nyilatkozat) megtekinthető.</p>
<h2 id="szabad--ingyenes">Szabad ≠ ingyenes</h2>
<p>Az angol “free software” kifejezés félrevezető lehet, ezért szokás az “Open source” vagy a “Free/Libre” kifejezést használni.</p>
<p>Szoftver lehet ingyenes, de nem nyílt:</p>
<ul>
<li>Visual Studio Community</li>
<li>Unity Personal</li>
</ul>
<p>Illetve nyílt forrású szoftverért is lehet pénzt kérni, üzleti célre felhasználni:</p>
<ul>
<li><a href="https://buy.ubuntu.com/">Ubuntu terméktámogatással</a></li>
<li><a href="https://www.redhat.com/en/store/linux-platforms">Red Hat előfizetés</a></li>
</ul>
<h2 id="licencek">Licencek</h2>
<p>A szabad szoftverekhez sokszor nem írnak saját licencet, hanem egy meglévőt használnak fel. Sok esetben ezek a licencek átestek a “tűzkeresztségen”, bíróság előtt is megállták a helyüket.</p>
<p><a href="https://www.gnu.org/licenses/license-list.html">Egy nem teljes lista gyakran használt szabad szoftver licencekről</a></p>
<h3 id="copyleft">Copyleft</h3>
<p>Copyleft licenc alatt azt értjük, hogy a származtatott műveket is ugyanazon a licenc alatt kell kiadni.</p>
<p>Ilyen pl. a GNU GPL: ha GPL-es komponenst használunk a szoftverben, akkor a GPL feltételei szerint ki kell adni a saját programunk forrását is, GPL alatt licencelve.</p>
<h3 id="nem-copyleft-megengedő">Nem copyleft, megengedő</h3>
<p>A szoftvert nem kötelező nyílt forrás alatt kiadni, de a legtöbb esetben az eredeti szerzőt meg kell jelölni a származtatott művekben.</p>
<p>Ilyen pl. az MIT licenc, amely alatt a példakódjaim szerepelnek.</p>
<p>Nézzük meg pl. a Chrome böngészőben:</p>
<ul>
<li>Help -> “Google Chrome is made possible by the Chromium open source project and other open source software.” szövegben a linkre kattintva</li>
<li>Vagy csak gépeljük be, hogy chrome://credits/</li>
</ul>
<h3 id="nem-szoftver-licencek">Nem szoftver licencek</h3>
<p>Képek, hanganyagok, dokumentáció esetében nem mindig értelmezhető a “forráskód” kifejezés. Ilyen esetekre a <a href="https://creativecommons.org/choose/">Creative Commons licenc-családot</a> szokták sokan használni.</p>
<h2 id="előnyök-hátrányok">Előnyök, hátrányok</h2>
<ul>
<li>Több szem többet lát hatás: ha a fejlesztőn, cégen kívül mások is belenézhetnek a forráskódba, akkor a hibákat könnyebben lehet megtalálni és javítani
<ul>
<li>Új funkciókat nem csak az eredeti fejlesztő készíthet</li>
<li><a href="https://www.infoworld.com/article/3253948/who-really-contributes-to-open-source.html#tk.twt_ifw">Egy 2018-as rangsor szerint</a> github-on a Microsoft alkalmazottai adtak a legtöbbet nyílt forrású projektekhez.</li>
</ul>
</li>
<li>A legtöbb esetben ingyenes, vagyis magáncélra történő felhasználásra, kipróbálásra nem kell hatalmas összeget befektetni
<ul>
<li>A Windows Server-nek 180 napos a próbaverziója, utána meg kell venni</li>
<li>Unity esetében csak bizonyos bevétel/cégméret alatt ingyenes</li>
</ul>
</li>
<li>Sok esetben szabad szoftver esetében is lehet terméktámogatást venni, lásd fejlebb</li>
</ul>
<p>Szabad szoftver azonban nem minden esetben alkalmazható:</p>
<ul>
<li>Üzleti titok esetében
<ul>
<li>A szoftver forráskódjából kiolvasható lenne minden</li>
</ul>
</li>
<li>Ha más, nem szabad szoftvert szeretnénk integrálni, aminek a forrását nem fedhetjük fel</li>
<li>Ahol a maga a szoftver licencelés a cég bevételi forrása
<ul>
<li>Játékok</li>
<li>Adobe Photoshop, Illustrator</li>
<li>3D renderelő, videóvágó szoftverek</li>
<li>Ezeknek általában nincs kellően jó minőségű, szabad alternatívája</li>
</ul>
</li>
<li>Megszokás
<ul>
<li>A LibreOffice gyakorlatilag mindenre képes, amire egy átlagos Office felhasználónak szüksége van</li>
</ul>
</li>
</ul>
<p>A nyílt forráskód és a szoftverminőség nem függ össze közvetlenül. Rengeteg jó és gyenge minőségű nyílt és zárt szoftver is létezik.</p>
<p>(Egy pont, amiben _általában__ a fizetős szoftverek előbbre járnak, az a User Interface design)</p>Nyílt forráskódú szoftvernek azt nevezzük, amikor a szoftver forráskódja a fejlesztők számára szabadon elérhető, és az módosítható, akár saját programba be is építhető. Köznyelvben a nyílt forráskódú (open-source) és a szabad (free/libre) szoftver fogalma rokon értelmű.Tesztelési módszerek2019-03-06T10:00:00+01:002019-03-06T10:00:00+01:00/2019/03/06/teszteles<p>Két fő fogalmat kell tisztázni, mielőtt a tesztelés módszereibe belekezdenénk:</p>
<ul>
<li><strong>Verifikáció:</strong> Amit elkészítünk, az helyesen működik-e?</li>
<li><strong>Validáció:</strong> A jó szoftvert készítjük-e, megoldja-e az ügyfél/felhasználó problémáját?</li>
</ul>
<p>A <em>validációt</em> akár már a tervezési fázisban, még az első programkódsor leírása előtt el tudjuk kezdeni. <em>Verifikálni</em> viszont értelemszerűen csak elkészült programot lehet.</p>
<p>A tesztelési módszereket sokféleképp lehet csoportosítani, és ideálisan mindegyiket használjuk a megfelelő szinteken:</p>
<h2 id="automatikus-vs-kézi">Automatikus vs. Kézi</h2>
<ul>
<li>Automatikus: a teszteket számítógép értékeli ki, és egyértelmű visszajelzést ad a sikerről.
<ul>
<li>Előny:
<ul>
<li>Könnyen megismételhető</li>
<li>Akár nagyon sok tesztesetet is végig tud nézni</li>
<li>Tesztelés közben (ideális esetben) nem hibázik</li>
</ul>
</li>
<li>Hátrány:
<ul>
<li>Teszteseteket pontosan meg kell fogalmazni</li>
<li>Nem minden esetben alkalmazható</li>
</ul>
</li>
</ul>
</li>
<li>Kézi: a teszteket ember végzi el.
<ul>
<li>Előny:
<ul>
<li>Olyan hibákra is ráakadhatunk, amelyeket előre nem specifikáltunk</li>
<li>“Szubjektív” hibákat is lehet ellenőrzni (pl. grafikus felület átláthatósága)</li>
</ul>
</li>
<li>Hátrány:
<ul>
<li>Az emberi erőforrás drága</li>
<li>Tesztelés közben nagyobb a hiba esélye, főleg repetitív feladatoknál</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="szisztematikus-vs-ad-hoc">Szisztematikus vs. ad-hoc</h2>
<ul>
<li>Szisztematikus
<ul>
<li>A teszteseteket előre definiáljuk, és előre megadott módszerrel értékeljük ki</li>
<li>Lehet automatikus vagy kézi</li>
</ul>
</li>
<li>Ad-hoc
<ul>
<li>A tesztelőnek szabad kezet adunk, nincsenek instrukciók</li>
<li>Csak kézi</li>
</ul>
</li>
</ul>
<h2 id="feketedoboz-vs-fehérdoboz">Feketedoboz vs. fehérdoboz</h2>
<ul>
<li>Feketedoboz:
<ul>
<li>Nem ismerjük a szoftver belső felépítését</li>
<li>Adott bemenetre adott kimenetet kell adni, a belső felépítést nem nézzük</li>
</ul>
</li>
<li>Fehérdoboz:
<ul>
<li>A bemenetet úgy választjuk meg, hogy minden elágazás, ciklus, belső függvény stb. legyen tesztelve</li>
<li><a href="https://en.wikipedia.org/wiki/Code_coverage">A kód-lefedettséget</a> IDE-eszközökkel lehet mérni</li>
</ul>
</li>
</ul>
<h2 id="tesztelési-szintek">Tesztelési szintek</h2>
<h3 id="egységtesztelés">Egységtesztelés</h3>
<p>Egyetlen osztályt, függvényt tesztelünk, a programtól izoláltan.</p>
<ul>
<li>Automatikus</li>
<li>Szisztematikus</li>
<li>Fehérdoboz
<ul>
<li>Lehet kombinálni code coverage eszközökkel lehet fehérdoboz is, pl. a siker feltétele, hogy a tesztesetek 100%-os lefedetséget produkáljanak</li>
</ul>
</li>
</ul>
<p><a href="/2019/03/02/nunit.html">Egységtesztelés a gyakorlatban, NUnit segítségével</a></p>
<h3 id="integrációs-tesztelés">Integrációs tesztelés</h3>
<p>Több komponenst együtt vizsgál, és azt dönti el, hogy az adott komponensek interfésze valóban megfelel-e a secifikáltaknak, és együtt is jól működnek-e.</p>
<ul>
<li>Többnyire automatikus</li>
<li>Többnyire szisztematikus</li>
<li>Fektetedoboz: a komponenseket kívülről vizsgáljuk</li>
</ul>
<h3 id="rendszerteszt">Rendszerteszt</h3>
<p>A teljes rendszert egészben, az összes komponensével vizsgáljuk, az éles környezettel megegyező körülmények közt.</p>
<ul>
<li>Többnyire kézi</li>
<li>Szisztematikus és ad-hoc</li>
<li>Feketedoboz</li>
</ul>
<h3 id="elfogadási-teszt-végtesztelés">Elfogadási teszt, végtesztelés</h3>
<p>A megrendelő, ügyfél végzi, hogy az elkészült rendszer valóban megfelel-e a specifikáltaknak.</p>
<p>A többi teszttel ellentétben (amelyeket a fejlsztés során folyamatosan végzünk) ez általában egyszer, a szoftver átadásakor történik meg.</p>
<h2 id="egyéb-gyakori-teszt-típusok">Egyéb gyakori teszt-típusok</h2>
<h3 id="regressziós-tesztelés">Regressziós tesztelés</h3>
<p>Nincs rendszer hiba nélkül. Ha egy hibát javítunk, akkor felvehetünk hozzá egy tesztesetet (egységtesztet, egy új esetet a kézi teszforgatókönyvbe stb.), hogy a későbbiekben ugyanez a hiba ne jöjjön elő.</p>
<p>Ezeket a teszteseteket hívjük összefoglaló néven regressziós teszteknek. Nem egy konkrét tesztelési szint, az egységteszttől a rendszertesztig bárhol jelentkezhet.</p>
<h3 id="alfa--és-béta-teszt">Alfa- és béta-teszt</h3>
<p>Az elfogadási/végtesztetlést el lehet végezni a rendszer elkészülte előtt is, hogy meggyőződjük a rendszer készültségéről, ismert hibáiról.</p>
<p>Hagyományosan alfa-tesztelésnek hívjuk, ha a tesztelést cégen belül végezzük, és béta-tesztelésnek, ha már egy zárt körű, de valódi felhasználók végzik.</p>
<p>A béta-tesztelés fogalma mára kezd kissé elmosódni, elég gyakori mindkét eset:</p>
<ul>
<li>A szoftver évekig béta-verzióban található, mert a fejlesztőcsapat nem meri rámondani, hogy készen van.
<ul>
<li>A Gmail 2004 áprilisától 2009 júliusáig volt béta verzióban, <strong>5 évig</strong></li>
<li>Lásd az “örök béta” (<a href="https://en.wikipedia.org/wiki/Perpetual_beta">Perpetual beta</a>) fogalmát</li>
</ul>
</li>
<li>A szoftvert kellő tesztelés nélkül adják ki, teli hibával, amelyek csak a későbbi frissítések során lesznek csak kijavítva
<ul>
<li>Ez általában annak a jele, hogy amit kiadtak, az valójában csak a béta-verzió lenne</li>
<li>Sokan ennek tartják a “Fallout 76” játék kezdeti kiadását</li>
</ul>
</li>
</ul>
<h3 id="nem-funkcionális-tesztelés">Nem funkcionális tesztelés</h3>
<p>Azok a tesztek, amelyek nem a helyes / helyteenséget, hanem az egyéb paramétereket vizsgálják:</p>
<ul>
<li>Stressz-tesztelés: erőforrásigény mérése, extrém mennyiségű bemenet (pl. egyszerre sok felhasználó, nagy mennyiségű adat stb.) kezelése</li>
<li>Biztonsági tesztelés: hálózati, webes alkalmazások esetén kiemelkedően fontos</li>
<li>Használhatósági teszt: a felhasználói interfész könnyen kezelhető, megérthető
<ul>
<li>Akadálymentesítés:
<ul>
<li>Az alkalmazás használható-e képernyő-felolvasó szoftverekkel</li>
<li>Nem okoz-e problémát színtévesztőknek</li>
<li>Támogat-e nagy kontrasztú módot</li>
<li>Használható-e csak billentyűzettel / csak egérrel</li>
<li>…</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="folyamatos-tesztelés-continuous-testing">Folyamatos tesztelés (continuous testing)</h2>
<p>Az automatikus teszteket nem csak igény szerint, a fejlesztő indíthatja el, hanem commitoláskor, vagy bizonyos időközönként automatikusan lefuthatnak.</p>
<ul>
<li><a href="https://github.com/dotnet/roslyn">A hivatalos C# fordító</a> - a tesztek rendszeresen lefutnak, és a kimenetet egy zöld succeeded / piros failed üzenet jelzi a táblázatban</li>
<li>A <a href="https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly">Mozilla Firefox böngésző legfrissebb változatából</a> naponta 2x készül egy újabb változat. Ezt szokás “nightly”, éjszakai verziónak hívni. A fordítás, tesztelés sok esetben több órás feladat, ezért szokás éjszakára beütemezni, hogy a fejlesztőknek ne napközben kelljen várni rá. Ha délután befejezte a munkáját, másnapra a teljes böngészővel együtt végezheti az integrációs / rendszertesztet.</li>
</ul>
<h2 id="dokumentumok">Dokumentumok</h2>
<p>A kézi szisztematikus teszteléshez, azért hogy a tesztelő tudja, mit is kell vizsgálnia, két dokumentum szükséges:</p>
<ul>
<li>Tesztforgatókönyv, amely leírja, hogy pontosan hova kell kattintani, mit kell begépelnie a tesztelőnek
<ul>
<li>Szerepel benne az elvárt, helyes kimenet is</li>
</ul>
</li>
<li>Tesztjegyzőköny, amit a tesztelő készít a forgatókönyv alapján, szerepel benne:
<ul>
<li>Ki, mikor, hol, melyik eszközön végezte a tesztelést</li>
<li>Az elvégzett teszteket, azok tényleges kimenetét, és a teszt értékelését (helyes / helytelen)</li>
</ul>
</li>
</ul>
<p>Ha a jegyzőkönyvet sablonként készítjük el, a két dokumentum kombinálható is.</p>
<p>Jegyzőkönyvet készíthetünk ad-hoc teszteléshez is, ebben az esetben a jegyzőkönyvben kell szerepelnie az elvégzett lépéseknek, az elvárt és a tényleges kimenetnek.</p>
<h2 id="további-olvasnivalók">További olvasnivalók</h2>
<p>Ez csak egy ízelítő a tesztelés témaköréből. Rengeteg teszmódszer létezik, és bármely említett elemet még ki lehetne fejteni részletesebben.</p>
<ul>
<li><a href="/2019/03/02/nunit.html">Egységtesztelés NUnit segítségével</a></li>
<li><a href="https://en.wikipedia.org/wiki/Software_testing">Wikipedia: Software testing</a></li>
</ul>Két fő fogalmat kell tisztázni, mielőtt a tesztelés módszereibe belekezdenénk:Jogi alapismeretek2019-03-04T10:00:00+01:002019-03-04T10:00:00+01:00/2019/03/04/jogi-alapismeretek<p>A jogi ismeretek legfontosabb szabálya, hogy <strong>ha bármi nem 100%-osan világos, akkor ügyvédet kell megkérdezni</strong>. Jogi problémák esetében sokat veszthetünk, ha “Úgy hallottam, hogy…”, “Szerintem…” típusú tanácsokra hagyatkozunk. Ebbe természetesen az alábbi összefoglaló is beletartozik, és nem tekinthető jogi tanácsadásnak. A leírás pedig csak összefoglaló, nem teljeskörű, és csak a magyar jogrendszerre érvényes.</p>
<p>Ettől függetlenül azért célszerű tisztában lenni a legfontosabb, szoftverfejlesztés során előkerülő jogi szabályokkal:</p>
<ul>
<li>Tudjuk, hogy milyen szoftvert, asset-et használhatunk fel, és milyen feltételek mellett</li>
<li>Tudjuk, hogy a saját termékünket milyen módokon lehet megvédeni.</li>
</ul>
<p>Itt most három fajta oltalmat fogunk megvizsgálni:</p>
<ul>
<li>Szerzői jog</li>
<li>Védjegy</li>
<li>Szabadalom</li>
</ul>
<h2 id="szerzői-jog-copyright">Szerzői jog (copyright)</h2>
<p>A szerzői jog a kreatív alkotásokat védi. Ilyen például egy regény, vers, festmény, és természetesen egy számítógépes program is. Ebbe beleértjük a kész programot, annak a forráskódját, és bármely átmeneti állapotban lévő változatát is.</p>
<p>A szerzői jog azt jelenti, hogy a szerző engedélye nélkül:</p>
<ul>
<li>A műről nem szabad másolatot készíteni</li>
<li>Nem szabad nyilvánosan előadni</li>
<li>Nem szabad származtatott művet készíteni</li>
</ul>
<p>Ez alól csak <a href="https://hu.wikipedia.org/wiki/Szerz%C5%91i_jog#A_szabad_felhaszn%C3%A1l%C3%A1s">nagyon kevés, korlátozott esetben</a> lehet eltérni.</p>
<p>Származtatott mű az, amelynek az eredeti mű a szerves részét képezi, pl.</p>
<ul>
<li>Egy zeneszám remix-elt változata</li>
<li>A zeneszám dalszövege szöveges formában</li>
<li>Egy regény megfilmesítése</li>
<li>Egy regény, amely másik regény szereplőit használja</li>
<li><strong>Egy program, amely egy másik programot / osztálykönyvtárt használ.</strong></li>
</ul>
<p>A szerzői jog <strong>automatikus</strong>, a mű megalkotása pillanatától érvényben van, külön nem kell kérelmezni.</p>
<p>A szerzői jog a (legutolsó) <strong>szerző halála utáni 70 évig</strong> védi a művet, amely ezután közkinccsé válik. Előzetes engedély nélkül, bármilyen célra fel lehet használni. A szerző persze saját maga is közkinccsé teheti a művét.</p>
<p>Gyakori tévhitek:</p>
<ul>
<li>A forrásmegjelölés kötelező
<ul>
<li>Csak akkor, ha a szerző előírja. Sok zárt forrású szoftver esetében pl. nem kötelező.</li>
<li>Közkincs esetében nincs ilyen előírás.</li>
</ul>
</li>
<li>Ha a forrás/szerző jelölve van, akkor az ingyenes terjesztés (pl. YouTube-ra feltöltés, saját programban felhasználás) legális
<ul>
<li>A szerző engedélye nélkül nem az.</li>
</ul>
</li>
<li>Záródolgozat esetében azért tilos a plágium, mert a szerzőtől nem kértünk engedélyt
<ul>
<li>A plágium akkor is tilos, ha a szerzőtől amúgy kaptunk engedélyt.</li>
<li>A plágium azért tilos, mert a dolgozat célja a saját tudás bemutatása, nem másoké.</li>
</ul>
</li>
<li>NuGet-tel / más csomagkezelővel letöltött library-t minden gond nélkül fel lehet használni
<ul>
<li>Bár az esetek többségében a felhasználás valóban ingyenes, a licencfeltételeket attól még be kell tartani, pl. a szerző megjelölését.</li>
</ul>
</li>
<li>Ötletet le lehet védetni
<ul>
<li>Csak konkrét műveket véd a szerzői jog</li>
</ul>
</li>
</ul>
<p>Gyakori kérdések és válaszok:<br />
<a href="https://euipo.europa.eu/ohimportal/hu/web/observatory/faqs-on-copyright-hu">https://euipo.europa.eu/ohimportal/hu/web/observatory/faqs-on-copyright-hu</a></p>
<p>A Berni Egyezmény szerint a szerzői jog <a href="https://en.wikipedia.org/wiki/Berne_Convention#/media/File:Berne_Convention_signatories.svg">a világ szinten minden országában</a> érvényes és gyakorolható (bár apró eltérések lehet a konkrét törvényekben, pl. az oltalom hosszát illetően), és egymás szerzői műveit elismerik.</p>
<h3 id="licencszerződés">Licenc(szerződés)</h3>
<p>A licencszerződés akkor készül, ha a szerző másokat szeretne a fenti jogokkal, vagy egy részével felruházni. Ahhoz, hogy egy regényt ki tudjon adni, ahhoz például szükséges a nyomdának, a kiadónak, a könyvesboltoknak engedélyt adni a sokszorosításra.</p>
<p>Hasonlóan, ha egy szoftvert “megveszünk”, akkor nem kapjuk meg automatikusan az összes jogot, csak azokat, amelyek a licencszerződésben szerepelnek.</p>
<p>Licencszerződés készülhet egyoldalúan:</p>
<ul>
<li>Egy átlag felhasználó vagy elfogadja a feltételeket, vagy nem veszi meg / nem használja a szoftvert.
<ul>
<li>Szabad szoftverek esetében is</li>
</ul>
</li>
</ul>
<p>De lehet személyre szabott is:</p>
<ul>
<li>Pl. Megrendelésre készült szoftver esetén</li>
<li>Több szabad szoftvernek van zárt forrású változata is, ahol a szabad változat ingyenes, a nem nyílt változat fizetős</li>
</ul>
<p>A szabad szoftvereket részletesebben egy külön bejegyzés alatt tárgyalom. (TODO)</p>
<h2 id="védjegy-trademark">Védjegy (trademark)</h2>
<p>Termékek, szolgáltatások <strong>megkülönböztetésére</strong> szolgál, pl.: márka, logó. A célja, hogy más nem használhassa ugyanazt, vagy hasonló megnevezést, jelölést a vásárlók megtévesztésére.</p>
<p>A védelemet kérni kell, <strong>nem automatikus</strong>, és folyamatos díja van. Nincs maximális ideje.</p>
<p>Az, hogy milyen termék/szolgáltatás eshet védjegyoltalom alá, és milyen szövegre, ábrára, hangra, alakzatra stb. adható meg a védjegyoltalmat, rendkívül részletes. Irányelvként:</p>
<ul>
<li>Nem lehet fajtanév, pl. “Alma” márkájú gyümölcs
<ul>
<li>De <a href="https://en.wikipedia.org/wiki/Apple_Inc.">számítógép</a>, vagy <a href="https://en.wikipedia.org/wiki/Apple_Records">zenekiadó</a> igen</li>
</ul>
</li>
<li>Termékkategóriánként lehet ugyanaz a levédett kifejezés, amennyiben nincs félreértésre adó ok
<ul>
<li>Az Apple példára ez is igaz</li>
<li>Senki nem fogja összekeverni az <a href="https://en.wikipedia.org/wiki/Unix">operációs rendszert</a> az <a href="https://www.unixauto.hu/">autóalkatrészekkel</a></li>
</ul>
</li>
<li>El lehet veszteni, többek közt:
<ul>
<li>Nem használat esetén</li>
<li>Köznévvé válás esetén, pl. a bakelit, cellux, celofán, kuka, walkman szavak eredetileg mind védjegyek voltak.
<ul>
<li><a href="https://hu.wikipedia.org/wiki/Fajtan%C3%A9vv%C3%A9_v%C3%A1lt_v%C3%A9djegy#P%C3%A9ld%C3%A1k_fajtan%C3%A9vv%C3%A9_v%C3%A1lt_v%C3%A9djegyekre">Egy részletesebb lista</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<p>A védjegyoltalmat minden országban külön-külön kell kérelmezni (nemzetközi egyezmények előfordulhatnak, pl. az EU esetében).</p>
<h2 id="szabadalom-patent">Szabadalom (patent)</h2>
<p>Szabadalmaztatni <strong>új</strong>, gyakorlatban is alkalmazható <strong>találmányokat</strong> lehet.</p>
<p>Azt, hogy pontosan, mit értünk újnak és találmánynak, több oldalon keresztül lehetne tárgyalni, és a végső döntést az adott ország szabadalmi hivatala (itthon a Szellemi Tulajdon Nemzeti Hivatala) hozza meg.</p>
<p>Ami számunkra fontos, a szoftver, önmagában nem szabadalmaztatható, mert a programozás a matematika egy alkalmazott ága, ezért nem minősül találmánynak. Azonban szoftver által vezérelt találmány egészében már szabadalmaztatható:</p>
<ul>
<li>Önvezető autó (szoftverrel együtt)</li>
<li>Új rendszerű kazán (a vezérlővel együtt) stb.</li>
</ul>
<p>Ha mégis sikerült egy személynek / cégnek szoftvert szabadalmaztatni, az az esetek többségében az USÁ-ban történik, és mint az egyik legnagyobb piac, nem mehetünk el mellette. A szoftverszabadalmak oka kettős:</p>
<ul>
<li>Az USÁ-ban találmányok mellett ún. <em>üzleti jellegű eljárás</em>-t is lehet szabadalmaztatni, amelyet sokan használnak a tisztán szoftveres szabadalmak “elbújtatására”</li>
<li>A szabadalmi hivatal nem feltétlen tudja megállapítani, hogy mi számít újnak, és mi nyilvánvalónak, így sok érvénytelen szabadalom keletkezik, amelyeket csak hosszas bírósági útan lehet csak érvényteleníteni</li>
</ul>
<p>Ezért ha itthon nem is, amerikában tevékenykedő cégek esetében a szoftvert is érdemes megpróbálni szabadalmaztatni.</p>
<p>A szabadalom természetesen <strong>nem automatikus</strong>, kérvényezni kell, és éves díja van. Az oltalmi idő <strong>maximálisan 20 év</strong>.</p>
<p>A szabadalom oltalmazásáért a tulajdonos kizárólagos jogot kap a találmány hasznosítására, még akkor is, ha tőle függetlenül egy másik feltaláló ugyanazt megvalósítja. Cserébe a szabadalmat nyilvánosságra kell hozni, és a védelem a 20 év leteltével megszűnik.</p>
<p>A szabadalmakat országonként kell kérelmezni.</p>A jogi ismeretek legfontosabb szabálya, hogy ha bármi nem 100%-osan világos, akkor ügyvédet kell megkérdezni. Jogi problémák esetében sokat veszthetünk, ha “Úgy hallottam, hogy…”, “Szerintem…” típusú tanácsokra hagyatkozunk. Ebbe természetesen az alábbi összefoglaló is beletartozik, és nem tekinthető jogi tanácsadásnak. A leírás pedig csak összefoglaló, nem teljeskörű, és csak a magyar jogrendszerre érvényes.Egységtesztelés2019-03-02T10:00:00+01:002019-03-02T10:00:00+01:00/2019/03/02/nunit<p>A tesztelés legalacsonyabb szintje az <strong>egységtesztelés</strong> (unit testing), mivel a programnak a legkisebb, önállóan is értelmes egységeit teszteli. Ez OOP programnyelvekben a <strong>függvény</strong> és az <strong>osztály</strong>, de többnyire az utóbbi.</p>
<p>Az egységtesztelés célje, hogy meggyőződjünk arról, hogy a programunk építőkockái önállóan is helyesen működjenek – csak helyes elemekből lehet jól működő alkalmazást építeni.</p>
<p>Egy összetettebb alkalmazás sok kisebb komponensből áll, a teszteléshez ezért rendkívül sok bemeneti kombinációt kellene kipróbálni, ami hasonló mértékű kimenetet produkál. A tesztek száma egy közepes méretű alkalmazásban is minden gond nélkül elérheti a több száz, vagy akár ezer esetet is, amit emberi erőforrással már lehetetlen kivitelezni. Emiatt az egységtesztelés szinte kivétel nélkül <strong>automatizált</strong>.</p>
<p>A legnépszerűbb C#-hoz használt keretrendszeren keresztül, az NUnit segítségével mutatom be az egységtesztelés mikéntjét.</p>
<p><a href="https://github.com/nunit/docs/wiki/NUnit-Documentation">Az NUnit teljes dokumentációja angol nyelven elérhető.</a></p>
<h2 id="előkészület">Előkészület</h2>
<p>Telepítsük az alábbi két nuget csomagot a projektünkhöz:</p>
<ul>
<li>NUnit - a teszteléshez szükséges osztályok</li>
<li>NUnit3TestAdapter - Visual Studio integráció</li>
</ul>
<p>A példa alkalmazásunkban az alábbi osztályt fogjuk tesztelni:</p>
<pre><code class="csharp">class Bank
{
// Egy létező számlára pénzt helyez
public void EgyenlegFeltolt(string szamlaszam, ulong osszeg)
{
// ...
}
// Új számlát nyit a megadott névvel, számlaszámmal
public void UjSzamla(string nev, string szamlaszam)
{
// ...
}
// Két számla között utal.
// Ha nincs elég pénz a forrás számlán, akkor
public bool Utal(string honnan, string hova, ulong osszeg)
{
// ...
}
// Lekérdezi az adott számlán lévő pénzösszeget
public ulong Egyenleg(string szamlaszam)
{
// ...
}
}
// Nem létező számla esetén dobhatjuk bármely függvényből
class HibasSzamlaszamException: Exception
{
public HibasSzamlaszamException(string szamlaszam)
: base("Hibas szamlaszam: " + szamlaszam)
{
}
}
</code></pre>
<p>TODO: teljes projekthez git repó link</p>
<h2 id="tesztesetek-írása">Tesztesetek írása</h2>
<p>A teszteket az osztály <em>specifikációja</em> alapján tudjuk elkészíteni, amit a jelen esetben a függvényekhez írt kommentek fognak jelképezni (egy valódi specifikáció természetesen ennél reszletesebb).</p>
<p>A teszteket általában elkülönítjük a valódi programkódtól (pl. egy külön “Tests” mappába helyezzük őket) az átláthatóság kedvért. Tegyünk mi is így, a projektben hozzunk létre egy ilyen mappád, és azon belül egy “BankTest” osztályt:</p>
<p><img src="/assets/img/nunit_testfolder.png" alt="Teszt mappa" /></p>
<p>Az osztályt jelöljük meg a TestFixture attribútummal, ezzel jelölve, hogy az osztály teszteseteket fog tartalmazni.</p>
<pre><code class="csharp">using NUnit.Framework;
[TestFixture]
public class BankTest
{
}
</code></pre>
<p>Az általános irányelv az, hogy egy osztály teszteléséhez egy tesztsor tartozik, de a projekttől függően természetesen ettől el lehet térni.</p>
<p>Az első tesztelendő funkciónk a számla létrehozása legyen - mégpedig, hogy új számla nyitásakor nem szabad, hogy pénz legyen a számlán.</p>
<pre><code class="csharp">using NUnit.Framework;
[TestFixture]
public class BankTest
{
[TestCase]
public void UjSzamlaEgyenlegNulla()
{
Bank b = new Bank();
b.UjSzamla("TesztNev", "1234");
Assert.AreEqual(0, b.Egyenleg("1234"));
}
}
</code></pre>
<p>Az Assert osztály függvényei segítségével <strong>állításokat</strong> fogalmazhatunk meg a kóddal kapcsolatban, amiknek igaznak kell lenniük. Az Assert.AreEqual() metódusa például azt állítja, hogy a két paraméter egyenlő. Az első paraméter az <em>elvárt</em> érték, a második pedig a <em>tényleges</em>, a program által kiszámolt érték.</p>
<p>Ha a kettő tényleg egyezik, akkor a tesztünk sikeres, ellenkező esetben pedig sikertelen.</p>
<p>Vegyünk fel egy másosikat is:</p>
<pre><code class="csharp">using NUnit.Framework;
[TestFixture]
public class BankTest
{
[TestCase]
public void UjSzamlaEgyenlegNulla()
{
Bank b = new Bank();
b.UjSzamla("TesztNev", "1234");
Assert.AreEqual(0, b.Egyenleg("1234"));
}
[TestCase]
public void PenzBetesz()
{
Bank b = new Bank();
b.UjSzamla("TesztNev", "1234");
b.EgyenlegFeltolt("1234", 5000);
Assert.AreEqual(5000, b.Egyenleg("1234"));
}
}
</code></pre>
<p>Minél több ilyen állítást fogalmazunk meg, minél több különböző bemenettel teszteljük az osztályunkat, annál kisebb az esélye, hogy a későbbiekben felderítetlen hibával találkozzunk.</p>
<p>A teszteket a “Test -> Run -> All Tests” segítségével futtathatjuk.</p>
<p>A sikeres teszteseteket szokás szerint zölddel, a sikertelen eseteket pirossal jelöli az IDE:</p>
<p><img src="/assets/img/nunit_testrun.png" alt="Sikeres és sikertelen tesztesetek" /></p>
<p>Sikertelen teszt esetén:</p>
<ul>
<li>Ellenőrizzük, hogy a tesztet valóban jól írtuk-e meg;</li>
<li>Ha a teszt rendben van, akkor a programunkat (a Bank osztályt) kell javítani.</li>
</ul>
<h2 id="egyéb-assert-függvények">Egyéb Assert függvények</h2>
<p>Az AreEqual metódussal elvileg mindent meg lehet oldani, de ha könnyen olvasható teszteket szeretnénk, akkor célszerű lehet az egyéb függvényeit is használni. A teljesség igénye nélkül, két példát mutatok be:</p>
<h3 id="assertfalse">Assert.False</h3>
<p>A teszt akkor sikeres, ha a paraméterként kapott érték hamis:</p>
<pre><code class="csharp"> [TestCase]
public void TestSikertelenUtalas()
{
Bank b = new Bank();
b.UjSzamla("TesztNev", "1234");
b.UjSzamla("TesztNev2", "5678");
var sikeres = b.Utal("1234", "5678", 10000);
Assert.AreEqual(0, b.Egyenleg("1234"));
Assert.AreEqual(0, b.Egyenleg("5678"));
Assert.False(sikeres);
}
</code></pre>
<p>A fenti példa azt ellenőrzi, hogy mi történik abban az esetben, ha a számlán nincs elég pénz a sikeres utaláshoz:</p>
<ul>
<li>A számlák egyenlege nem változik (marad nulla);</li>
<li>A függvény visszatérési értéke hamis.</li>
</ul>
<p>A teszt akkor sikeres, ha mindhárom Assert teljesül.</p>
<h3 id="assertthrowst">Assert.Throws<T></h3>
<p>A teszt akkor sikeres, ha a paraméterként átadott függvény a megadott típusú kivételt dobja:</p>
<pre><code class="csharp"> [TestCase]
public void UtalasNemLetezoSzamlara()
{
Bank b = new Bank();
b.UjSzamla("TesztNev", "1234");
b.EgyenlegFeltolt("1234", 15000);
b.UjSzamla("TesztNev2", "5678");
Assert.Throws<HibasSzamlaszamException>(
() =>
{
b.Utal("1234", "9999", 10000);
}
);
Assert.AreEqual(15000, b.Egyenleg("1234"));
Assert.AreEqual(0, b.Egyenleg("5678"));
}
</code></pre>
<p>Az Utal függvény olyan számlára próbál utalni, amit nem hoztunk létre, ezért a függvény kivételt dob. Jelen esetben <em>ez a helyes viselkedés</em>. Ha az Utal függvény nem dobna kivételt, vagy más kivételt dob, akkor a teszt sikertelen lesz.</p>
<p>Természetesen azt is ellenőrizzzük, hogy a sikertelen utalás után a számlán lévő pénzösszegek nem változtak.</p>
<p>A <code>() => { /* ... */ }</code> kódrészlet egy <a href="/2018/10/16/lambda-fuggveny.html">lambda függvény</a>, így tudunk hagyományos értékek/objektumok helyett kódrészletet átadni a Throws függvénynek.</p>
<p>A tesztesetekhez állítások megfogalmazására rengeteg módszer van, a teljes dokumentáció elérhető itt: <a href="https://github.com/nunit/docs/wiki/Assertions">https://github.com/nunit/docs/wiki/Assertions</a></p>
<h2 id="setup-és-teardown">SetUp és TearDown</h2>
<p>Észrevehettük, hogy a bank osztályt minden teszteset elején létre kell hozni. Azért, hogy ezt a lépést ne kelljen minden alkalommal megismételni:</p>
<ul>
<li>Felvehetjük a Bank objektumot privát változóként és</li>
<li>Minden teszteset elején létrehozhatjuk automatikusan.</li>
</ul>
<pre><code class="csharp">using NUnit.Framework;
[TestFixture]
public class BankTest
{
Bank b;
[SetUp]
public void Setup()
{
b = new Bank();
}
[TestCase]
public void UjSzamlaEgyenlegNulla()
{
b.UjSzamla("TesztNev", "1234");
Assert.AreEqual(0, b.Egyenleg("1234"));
}
[TestCase]
public void PenzBetesz()
{
b.UjSzamla("TesztNev", "1234");
b.EgyenlegFeltolt("1234", 5000);
Assert.AreEqual(5000, b.Egyenleg("1234"));
}
}
</code></pre>
<p>A SetUp attribútummal megjelölt függvényt a rendszer minden teszteset futtatása előtt végrehajtja.</p>
<p>A párja a ritkábban használt TearDown, amit ideiglenes fájlok, adatbázisok stb. lezárása lehet használni:</p>
<pre><code class="csharp">[TearDown]
public void Teardown()
{
// ...
}
</code></pre>
<h2 id="előnyök-követelmények">Előnyök, követelmények</h2>
<ul>
<li>Több száz/ezer tesztesetet emberi erővel nem lehet végignézni</li>
<li>Ha egy tesztet megírtunk, akkor az a jövőben folyamatosan fut. Biztosak lehetünk, hogy egy 10 éve javított hiba nem jön vissza.</li>
<li>A tesztek írásakor rájöhetünk, hogy a specifikáció hiányos:
<ul>
<li>Pl. Mi történik, ha két számlát ugyanazzal a számlaszámmal szeretnék nyitni?</li>
</ul>
</li>
</ul>
<p>Az egységteszthez követelmények:</p>
<ul>
<li>A kimenetnek egyértelműnek kell lennie, Igen/Nem jelleggel kapjuk a végeredményt.</li>
<li>A tesztek sorrendje nem fix:
<ul>
<li>Lehet, hogy csak egy tesztesetet futtatunk</li>
<li>Lehet, hogy a sorrend nem egyezik a kódban felvett sorrenddel</li>
<li>Ezért a tesztek nem épülhetnek egymásra, mindegyiket tiszta lappal kell kezdeni</li>
</ul>
</li>
<li>A fentiekből következik, hogy lehetőleg kerüljük a fájlok, adatbáziskapcsolatok stb. használatát
<ul>
<li>Ha semmiképp sem tudjuk elkerülni, akkor a tesztben kell megfogalmazni a tesztfájlok tartalmát, az adatbázis szerkezetét/tartalmát stb.</li>
<li>Adatbázissal együtt történő teszteléshez egy jó eszköz az <a href="https://www.sqlite.org/inmemorydb.html">SQLite memória-adatbázisa</a> (fájlnévnek “:memory:”-t kell megadni).</li>
</ul>
</li>
</ul>
<p>A program nem minden eleme tesztelhető ezen a szinten!</p>
<ul>
<li>Az egységteszt alkalmatlan pl. UI tesztelésre</li>
<li>Ha a hiba a komponensek interakcióiból, vagy hibás specifikációból következik, csak magasabb szintű, pl. integrációs tesztek veszik csak észre.</li>
</ul>
<h2 id="más-programnyelvek">Más programnyelvek:</h2>
<p>C#-hoz, és más nyelvekhez is tartozik akár több tesztelési keretrendszer.</p>
<p>TODO: a fenti példát elkészíteni más programnyelveken:</p>
<ul>
<li>Java: JUnit</li>
<li>PHP: PHPUnit</li>
<li>JavaScript: mocha + chai</li>
</ul>A tesztelés legalacsonyabb szintje az egységtesztelés (unit testing), mivel a programnak a legkisebb, önállóan is értelmes egységeit teszteli. Ez OOP programnyelvekben a függvény és az osztály, de többnyire az utóbbi.Absztrakt adattípusok2018-12-20T10:00:00+01:002018-12-20T10:00:00+01:00/2018/12/20/absztrakt-adattipusok<p>Az adatszerkezeteket kétféleképp is megkülönböztethetjük.</p>
<p>Felépítés szerint (pl. tömb, fa, hash tábla), vagy pedig a rajtuk elvégezhető műveletek szerint:</p>
<ul>
<li>Adatszerkezetek (a felépítés szerinti)</li>
<li>Absztrakt adattípusok (műveletek szerint)</li>
</ul>
<p>Itt most az utóbbiból mutatok be néhányat.</p>
<h2 id="verem">Verem</h2>
<ul>
<li>Angolul stack</li>
<li>LIFO (Last-in First-out)</li>
</ul>
<p>A verem két műveletet definiál:</p>
<ul>
<li>push -> beleteszegy elemet</li>
<li>pop -> kiveszi azt az elemet, amelyet <em>legutóljára</em> tettünk bele.</li>
</ul>
<p>Az alábbi módon tudjuk elképzelni:</p>
<pre>
v = új üres Verem() // []
v.push(5) // [5]
v.push(10) // [5, 10]
v.push(-1) // [5, 10, -1]
Kiír(v.pop()) // [5, 10] Ki: -1
Kiír(v.pop()) // [5] Ki: 10
</pre>
<h2 id="sor">Sor</h2>
<ul>
<li>Angolul queue</li>
<li>FIFO (First-in First-out)</li>
</ul>
<p>A sor szintén két műveletet definiál:</p>
<ul>
<li>enqueue -> beleteszegy elemet</li>
<li>dequeue -> kiveszi azt az elemet, amelyet <em>legelőször</em> tettünk bele.</li>
</ul>
<pre>
s = új üres Sor() // []
s.enqueue(5) // [5]
s.enqueue(10) // [5, 10]
s.enqueue(-1) // [5, 10, -1]
Kiír(s.dequeue()) // [10, -1] Ki: 5
Kiír(s.dequeue()) // [-1] Ki: 10
</pre>
<h2 id="prioritási-sor">Prioritási sor</h2>
<ul>
<li>Angolul priority queue</li>
<li>A kivétel sorrendje nem féltétlen a berakás sorrendjében történik
<ul>
<li>Az elemeket sorrendbe kell tudni rendezni</li>
<li>A legkisebb értéket veszi ki</li>
</ul>
</li>
</ul>
<pre>
ps = új üres PriritásiSor() // []
ps.hozzáad(5) // [5]
ps.hozzáad(10) // [5, 10]
ps.hozzáad(-1) // [5, 10, -1]
Kiír(ps.kivesz()) // [5, 10] Ki: -1
Kiír(ps.kivesz()) // [10] Ki: 5
</pre>
<h2 id="halmaz">Halmaz</h2>
<ul>
<li>Angolul set</li>
<li>Az elemeknek nincs előre definiált sorrendje</li>
<li>Minden elem csak egyszer szerepelhet</li>
<li>A leggyakrabban használt művelet a “bennevan(): logikai”, általában erre van optimizálva</li>
</ul>
<pre>
h = új üres Halmaz() // []
h.hozzáad(5) // [5]
h.hozzáad(1) // [5, 1]
h.hozzáad(11) // [5, 1, 11]
h.hozzáad(5) // [5, 1, 11]
h.bennevan(2) // Hamis
h.bennevan(5) // Igaz
</pre>
<h2 id="szótár">Szótár</h2>
<ul>
<li>Angolul map
<ul>
<li>Map: párosítás</li>
<li>Dictionary: szótár</li>
<li>Asszociatív tömb: associative array</li>
</ul>
</li>
<li>Minden értékhez tartozik egy <strong>egyedi</strong> kulcs, amely alapján lehet az értékeket kikeresni</li>
</ul>
<pre>
sz = új üres Szótár()
sz.hozzáad(5, "Gyurika")
sz.hozzáad(9, "Petike")
sz[5] // "Gyurika"
sz[10] // HIBA: nincs 10-es kulcs
</pre>
<p>A kulcs nem csak szám lehet, persze:</p>
<pre>
sz = új üres Szótár()
sz.hozzáad("Gyurika", 5.6)
sz.hozzáad("Petike", -8.7)
sz["Petike"] // -8.7
sz["Csőrike"] // HIBA: nincs "Csőrike" kulcs
</pre>
<h2 id="programkódban">Programkódban</h2>
<p>Programkódban természetesen nem csak a fenti pár művelet van definiálva, pl. szinte minden osztályon létezik a Count tulajdonság, a Contains() függvény stb. Érdemes használatkor átnézni őket.</p>
<h3 id="c">C#</h3>
<pre><code class="csharp">var stack = new Stack<int>();
stack.Push(5);
stack.Push(10);
stack.Push(-1);
Console.WriteLine(stack.Pop()); // -1
Console.WriteLine(stack.Pop()); // 10
var queue = new Queue<int>();
queue.Enqueue(5);
queue.Enqueue(10);
queue.Enqueue(-1);
Console.WriteLine(queue.Dequeue()); // 5
Console.WriteLine(queue.Dequeue()); // 10
// .NET-ben nincs beépített prioritási sor osztály,
// de az alábbi NuGet csomag segít:
// https://www.nuget.org/packages/OptimizedPriorityQueue/
// Az értéknek nem kell megegyeznie a prioritással!
var pq = new SimplePriorityQueue<int, int>();
pq.Enqueue(5, 5);
pq.Enqueue(10, 10);
pq.Enqueue(-1, -1);
Console.WriteLine(pq.Dequeue()); // 5
Console.WriteLine(pq.Dequeue()); // 10
var s = new HashSet<int>();
s.Add(5);
s.Add(1);
s.Add(11);
s.Add(5); // false visszatérési értékkel jelez, hogy már benne van
Console.WriteLine(s.Count); // 3
Console.WriteLine(s.Contains(2)); // false
Console.WriteLine(s.Contains(5)); // true
var d = new Dictionary<string, double>();
d.Add("Gyurika", 5.6);
d.Add("Petike", -8.7);
Console.WriteLine(d.ContainsKey("Csőrike")); // hamis
Console.WriteLine(d["Petike"]); // -8.7
Console.WriteLine(d["Csőrike"]); // KeyNotFoundException
</code></pre>
<h2 id="java">Java</h2>
<p>Java-ban a verem és sor adatszerkezeteket az ArrayDeque osztályon keresztül használhatjuk.</p>
<pre><code class="java">Deque<Integer> stack = new ArrayDeque<>();
// A végére rekjuk az elemet
stack.addLast(5);
stack.addLast(10);
stack.addLast(-1);
// ...és onnan is vesszük ki
System.out.println(stack.removeLast()); // -1
System.out.println(stack.removeLast()); // 10
// Ugyanaz az osztály, de a Queue interface-en keresztül használjuk
Queue<Integer> queue = new ArrayDeque<>();
queue.add(5);
queue.add(10);
queue.add(-1);
System.out.println(queue.remove()); // 5
System.out.println(queue.remove()); // 10
Queue<Integer> pq = new PriorityQueue<>();
pq.add(5);
pq.add(10);
pq.add(-1);
System.out.println(pq.remove()); // 5
System.out.println(pq.remove()); // 10
Set<Integer> s = new HashSet<>();
s.add(5);
s.add(1);
s.add(11);
s.add(5); // false visszatérési értékkel jelez, hogy már benne van
System.out.println(s.size()); // 3
System.out.println(s.contains(2)); // false
System.out.println(s.contains(5)); // true
Map<String, Double> d = new HashMap<>();
d.put("Gyurika", 5.6);
d.put("Petike", -8.7);
System.out.println(d.containsKey("Csőrike")); // hamis
System.out.println(d.get("Petike")); // -8.7
System.out.println(d.get("Csőrike")); // null
</code></pre>
<h3 id="php">PHP</h3>
<p>A PHP csak korlátozottan taralmaz dedikált adatszerkezeteket, amelyekkel a műveletek megvalósíthatók.</p>
<p>A tömbhöz azonban rengeteg segédfüggvény jár, amivel a verem/sor megoldható:</p>
<pre><code class="php">$stack = [];
$stack[] = 5; // push művelet
$stack[] = 10;
$stack[] = -1;
print( array_pop($stack) ); // -1
print( array_pop($stack) ); // 10
$queue = [];
$queue[] = 5; // enqueue művelet, ua. mint fent
$queue[] = 10;
$queue[] = -1;
print( array_shift($queue) ); // 5 - az elejéről veszi le, azaz dequeue
print( array_shift($queue) ); // 10
</code></pre>
<p>A PHP-s prioritási sor “fordítva” működik, először a legnagyobb értéket veszi ki.
Itt is meg lehet adni különböző prioritást az elemhez képest:</p>
<pre><code class="php">$pq = new SplPriorityQueue();
$pq->insert(5, 5);
$pq->insert(10, 10);
$pq->insert(-1, -1);
print( $pq->extract() ); // 10 (legnagyobb)
print( $pq->extract() ); // 5
</code></pre>
<p>Ha az elemeink, kulcsaink egész vagy string típusúak, akkor a tömbből akár halmaz és szótár is lehet.</p>
<pre><code class="php">$set = [];
$set[5] = true; // Hozzáadás a halmazhoz
$set[1] = true;
$set[11] = true;
$set[5] = true; // Duplán hozzáadás sem gond
print( count($set) ); // 3
print( isset($set[2]) ); // false
print( isset($set[5]) ); // true
unset($set[11]); // Törlés
$dict = [];
$dict["Gyurika"] = 5.6; // Hozzáadás a szótárhoz
$dict["Petike"] = -8.7;
print( isset($dict["Csőrike"]) ); // hamis
print( $dict["Petike"] ); // -8.7
print( $dict["Csőrike"] ); // Notice: Undefined index: Csőrike
unset($dict["Petike"]); // Törlés
</code></pre>
<p>Mivel egy tömb kulcsa csak egész szám vagy szöveg lehet, ez a módszer más típusokra nem működik. Ha halmaz elem, vagy a szótár kulcsa objektum, használhatjuk még az <a href="http://php.net/manual/en/class.splobjectstorage.php">SplObjectStorage</a> osztályt.</p>Az adatszerkezeteket kétféleképp is megkülönböztethetjük.A fontosabb gráf-algoritmusok2018-12-18T11:00:00+01:002018-12-18T11:00:00+01:00/2018/12/18/graf-algoritmusok<p>A gráfokon különféle algoritmusokat végezhetünk el, ehhez pedig először egy konkrét, objektum-orientált adatszerkezetet kell definiálnunk:</p>
<p><img src="/assets/img/graf-diagram.png" alt="OOP gráf" /></p>
<p>A gráf külső iterfészét a <em>Graf</em> osztály fogja jelenteni. Az ehhez tartozó metódusokkal fogjuk a gráfot kezelni, az algoritmusokat lefuttatni - a gráf használójának nem kell tudni a belső felépítésről, a Csucs és El osztályokról.</p>
<p>A tárolás módja <em>éllista</em> lesz, ahol minden élt az <em>El</em> osztály egy példánya fogja jelenteni. A könnyebb kezelhetőség kedvéért az élt úgy tároljuk el, hogy <em>mindkét irányt</em> felvesszük, vagyis ha szerepel az 1-3 él, akkor a 3-1 is szerepelni fog.</p>
<p>Emellett még eltároljuk a gráf csúcsait is egy-egy objektumban.</p>
<p>Ezt a két osztályt a későbbiekben ki lehet egészíteni, ha egy-egy élhez vagy csúcshoz még további adatot szeretnénk eltárolni.</p>
<p>A gráf <em>egyszerű</em> és <em>irányítatlan</em> lesz, de egyéb adatokkal (pl. súly, szín) ki lehet egészíteni.</p>
<p>A kontruktorban előre meg kell adni a csúcsok számát, ez később nem módosítható. Kezdetben a gráf egy élt sem tartalmaz.</p>
<p>A <em>hozzad</em> függvény segíségével adhatunk hozzá új éleket, a csúcs indexek (0 … N-1) megadásával. Törlésre jelenleg nincs mód.</p>
<p>A fenti osztályt az alábbi három példa-projekt meg is valósítja:</p>
<ul>
<li><a href="https://github.com/hgabor/GrafFeladat_CSharp">C# megvalósítás</a></li>
<li><a href="https://github.com/hgabor/GrafFeladat_Java">Java megvalósítás</a></li>
<li><a href="https://github.com/hgabor/GrafFeladat_PHP">PHP megvalósítás</a></li>
</ul>
<h2 id="bejárás">Bejárás</h2>
<p>Egy gráfot kétféleképp lehet bejárni. Kiválasztunk egy kezdőpontot, aztán:</p>
<ul>
<li>Széllességben folytatjuk
<ul>
<li>Először megvizsgáljuk a pont összes szomszédját</li>
<li>Majd azoknak az összes szomszédját</li>
<li>Stb.</li>
<li><a href="http://tamop412.elte.hu/tananyagok/algoritmusok/lecke24_lap1.html">Ezen az oldalon az első ábra rendkívül jól illusztrálja</a></li>
</ul>
</li>
<li>Mélységben folytatjuk
<ul>
<li>Először megvizsgáljuk a pont <strong>egy</strong> szomszédját</li>
<li>Majd annak <strong>egy</strong> szomszédját</li>
<li>Ha nem tudunk tovább menni, <strong>visszalépünk</strong>, és a pont egy másik szomszédjával folytatjuk</li>
<li>Ha elfogy, megint visszalépünk</li>
<li>Ha a kezdőponból is visszalépnénk, végeztünk</li>
<li><a href="http://tamop412.elte.hu/tananyagok/algoritmusok/lecke29_lap1.html">Ezen az oldalon az első pár ábra rendkívül jól illusztrálja</a></li>
</ul>
</li>
</ul>
<h3 id="a-szélességi-bejárás-algoritmusa">A szélességi bejárás algoritmusa</h3>
<p>A szélességi bejárásnál az elemeket egy <em>sor</em> adatszerkezetbe fűzzük, majd ebből kivéve vizsgáljuk az elemeket és a szomszédjaikat.</p>
<pre>
Gráf.SzelességiBejár(kezdopont: egész):
// Kezdetben egy pontot sem jártunk be
bejárt = új üres Halmaz()
// A következőnek vizsgált elem a kezdőpont
következők = új üres Sor()
következők.hozzáad(kezdőpont)
bejárt.hozzáad(kezdőpont)
// Amíg van következő, addig megyünk
Ciklus amíg következők nem üres:
// A sor elejéről vesszük ki
k = következők.kivesz()
// Elvégezzük a bejárási műveletet, pl. a konzolra kiírást:
Kiír(this.csúcsok[k])
Ciklus él = this.élek elemei:
// Megkeressük azokat az éleket, amelyek k-ból indulnak
// Ha az él másik felét még nem vizsgáltuk, akkor megvizsgáljuk
Ha (él.csúcs1 == k) és (bejárt nem tartalmazza él.csúcs2-t):
// A sor végére és a bejártak közé szúrjuk be
következők.hozzáad(él.csúcs2)
bejárt.hozzáad(él.csúcs2)
// Jöhet a sor szerinti következő elem
</pre>
<h3 id="a-mélységi-bejárás-bejárás-algoritmusa-1-változat">A mélységi bejárás bejárás algoritmusa (1. változat)</h3>
<p>Szinte teljes mértékben megegyezik a szélességi bejárással. A különbség, hogy a sor helyet <em>verem</em> adatszerkezetbe tesszük a pontokat:</p>
<pre>
Gráf.MélységiBejár(kezdopont: egész):
// Kezdetben egy pontot sem jártunk be
bejárt = új üres Halmaz()
// A következőnek vizsgált elem a kezdőpont
következők = új üres Verem()
következők.hozzáad(kezdőpont)
bejárt.hozzáad(kezdőpont)
// Amíg van következő, addig megyünk
Ciklus amíg következők nem üres:
// A verem tetejéről vesszük le
k = következők.kivesz()
// Elvégezzük a bejárási műveletet, pl. a konzolra kiírást:
Kiír(this.csúcsok[k])
Ciklus él = this.élek elemei:
// Megkeressük azokat az éleket, amelyek k-ból indulnak
// Ha az él másik felét még nem vizsgáltuk, akkor megvizsgáljuk
Ha (él.csúcs1 == k) és (bejárt nem tartalmazza él.csúcs2-t):
// A verem tetejére és a bejártak közé adjuk hozzá
következők.hozzáad(él.csúcs2)
bejárt.hozzáad(él.csúcs2)
// Jöhet a sor szerinti következő elem
</pre>
<h3 id="a-mélységi-bejárás-bejárás-algoritmusa-2-változat">A mélységi bejárás bejárás algoritmusa (2. változat)</h3>
<p>A verem helyett a <em>hívási stack</em>-et is felhasználhatjuk, így egy könnyebben megérthető, rekurzív algoritmust kapunk:</p>
<pre>
// A fő függvény, amivel a rekurziót elindítjuk
Graf.MélységiBejár(kezdőpont: egész):
bejárt = új üres Halmaz()
bejárt.hozzáad(kezdőpont)
this.MélységiBejárRekurzív(k, bejárt)
// Segédfüggvény, amely magát a rekurziót végzi
Gráf.MélységiBejárRekurzív(k: egész, bejárt: Halmaz):
// Elvégezzük a bejárási műveletet, pl. a konzolra kiírást:
Kiír(this.csúcsok[k])
Ciklus él = this.élek elemei:
// Megkeressük azokat az éleket, amelyek k-ból indulnak
// Ha az él másik felét még nem vizsgáltuk, akkor megvizsgáljuk
Ha (él.csúcs1 == k) és (bejárt nem tartalmazza él.csúcs2-t):
bejárt.hozzáad(él.csúcs2)
Gráf.MélységiBejárRekurzív(él.csúcs2, bejárt)
</pre>
<h2 id="összefüggőség">Összefüggőség</h2>
<p>Az összefüggőség vizsgálatához a gráfot be kell járni egy tetszőleges pontból, tetszőleges módszerrel. Ha minden elemet bejártunk, akkor a gráf összefüggő.</p>
<p>Úgy is elképzelhetjük, mint egy képszerkesztő alkalmazásban a <em>flood fill</em> eszközt: ha kimarad egy elem, akkor nem összefüggő, ha pedig az egészet sikerül beszínezni, akkor összefüggő.</p>
<p>Alakítsuk át egy picit mondjuk a szélességi bejárás algoritmusát:</p>
<pre>
Gráf.Összefüggő(): logikai
bejárt = új üres Halmaz()
következők = új üres Sor()
következők.hozzáad(0) // Tetszőleges, mondjuk 0 kezdőpont
bejárt.hozzáad(0)
Ciklus amíg következők nem üres:
k = következők.kivesz()
// Bejárás közben nem kell semmit csinálni
Ciklus él = this.élek elemei:
Ha (él.csúcs1 == k) és (bejárt nem tartalmazza él.csúcs2-t):
következők.hozzáad(él.csúcs2)
bejárt.hozzáad(él.csúcs2)
// A végén megvizsgáljuk, hogy minden pontot bejártunk-e
Ha bejárt.elemszám == this.csúcsokSzáma:
Vissza: igaz
Különben:
Vissza: hamis
</pre>
<h2 id="feszítőfa-készítése">Feszítőfa készítése</h2>
<p>A feszítőfa egy olyan fa, amely a pontosan az eredeti gráf pontjait tartalmazza, de az elék közül csak azokat, amelyekkel még fa (azaz körmentes) lesz a gráf.</p>
<p>Az algoritmus módszere, hogy egy bejárási algoritmus lényegében egy fát ír le - mindössze meg kell jegyeznünk, hogy melyik él mentén haladtunk végig. Ha olyan pontba futunk, amit már vizsgáltunk, akkor azt az élt már nem vesszük figyelembe, mert kört okozna.</p>
<p>Az algoritmus nyilván csak akkor ad helyes eredményt, ha a gráf összefüggő.</p>
<pre>
Gráf.Feszitőfa(): Gráf
// Új, kezdetben él nélküli gráf
fa = új Gráf(this.csúcsokSzáma)
// Bejáráshoz szükséges adatszerkezetek
bejárt = új üres Halmaz()
következők = új üres Sor()
// Tetszőleges, mondjuk 0 kezdőpont
következők.hozzáad(0)
bejárt.hozzáad(0)
// Szélességi bejárás
Ciklus amíg következők nem üres:
aktuálisCsúcs = következők.kivesz()
Ciklus él = this.élek elemei:
Ha él.csúcs1 == aktuálisCsúcs:
Ha bejárt nem tartalmazza él.Csúcs2-t
bejárt.hozzáad(él.csúcs2)
következők.hozzáad(él.Csúcs2)
// A fába is vegyük bele az élt
fa.hozzáad(él.Csucs1, él.csúcs2)
// Az eredményül kapott gráf az eredeti gráf feszítőfája
vissza: fa
</pre>
<h2 id="csúcs-színezés-mohó-algoritmussal">Csúcs-színezés mohó algoritmussal</h2>
<p>A feladat lényege, hogy mindn csúcsot úgy kell egy adott színre színezni, hogy az egymással szomszédos csúcsok ne legyenen egyszínűek. Gondoljunk egy térképre: <a href="https://geology.com/world/world-map.shtml">a szomszédos országok különböző színűek.</a></p>
<p>Az algoritmus attól mohó, hogy nem gondolkodik előre, nem foglalkozik azzal, hogy a lehető legkevesebb színt használja fel.</p>
<p>Az algoritmus kimenete egy (csúcs: egész) => (szín: egész) szótár lesz. A színt is 0…N-1 közötti egész számokkal jelöljük.</p>
<pre>
Gráf.MohóSzínezés(): Szótár(egész => egész)
színezés = új üres Szótár()
// Legrosszabb esetben minden csúcsot különböző színűre kell színezni,
// ezért ennyi szín elég lesz
maxSzín = this.csúcsokSzáma
Ciklus aktuálisCsúcs = 0-tól this.csúcsokSzáma - 1 -ig:
// Kezdetben bármely színt választhatjuk
választhatóSzínek = új Halmaz(), amely
0...maxSzín-1 elemekkel van feltöltve
// Vizsgáljuk meg a szomszédos csúcsokat:
Ciklus él = this.élek elemei:
Ha él.csúcs1 == aktuálisCsúcs:
// Ha a szomszédos csúcs már be van színezve,
// azt a színt már nem választhatjuk
Ha színezés.tartalmazKulcsot(él.csúcs2):
szín = színezés[él.csúcs2]
választhatóSzínek.kivesz(szín)
// A maradék színekből válasszuk ki a legkisebbet
választottSzín = Min(választhatóSzínek)
színezés.hozzáad(aktuálisCsúcs, választottSzín)
vissza: színezés
</pre>
<h2 id="útkeresés---dijkstra">Útkeresés - Dijkstra</h2>
<p>Az <strong>útkeresés</strong> azt az algoritmust jelöli, hogy egy adott keződőpontból hogyan lehet eljutni az összes többi pontba, a lehető legrövidebb idő (legkisebb súly) felhasználásával.</p>
<p>Ehhez a gráfunkat ki kell egészíteni, hogy az élek tartalmazzanak egy súly tulajdonságot is:</p>
<pre>
Él osztály:
csúcs1: egész
csúcs2: egész
súly: lebegőpontos, > 0
</pre>
<p>Az útkeresés <a href="https://hu.wikipedia.org/wiki/Dijkstra-algoritmus">Dijkstra algoritmusával</a> fog történni. Egy segédosztályt is definiálunk, amiben az átmeneti, ill. a végeredményt tároljuk. Minden csúcshoz egy ilyen objektum fog tartozni:</p>
<pre>
CsúcsAdat osztály:
// Ebből a csúcsból érkeztünk
forrásCsúcs: egész = -1
// Ennyi a minimun költség, kezdetben végtelen
költség: lebegőpontos = végtelen
// Már vizsgáltuk-e:
vizsgáltuk: logikai = hamis
</pre>
<p>Az algoritmus kimenete egy szótár, minden csúcshoz hozzárendeli a csúcshoz tartozó költséget, és hogy ahhoz a csúcshoz hogyan lehet eljutni.</p>
<pre>
Gráf.Dijkstra(kezdőpont: egész): Szótár(egész => CsúcsAdat)
csúcsAdatok = új Szótár()
Ciklus csúcsIndex = 0-tól this.csúcsokSzáma-1 -ig:
csúcsAdatok.hozzáad(csúcsIndex, új CsúcsAdat())
// A kezdőpont költsége 0, hiszen innen indulunk:
csúcsAdatok[kezdőpont].költség = 0
// Minden csúcsot megvizsgálunk, de nem a szokványos sorrendben
vizsgáltDarab = 0
Ciklus amíg vizsgáltDarab < this.csúcsokSzáma:
vizsgáltDarab++
// Segédfüggvény, amely kiválasztja a következő csúcst
// l. lentebb
vizsgáltCsúcs = this.KövetkezőCsúcs(csúcsAdatok)
csúcsAdatok[vizsgáltCsúcs].vizsgáltuk = igaz
// Vizsgáljuk meg a szomszédos csúcsokat:
Ciklus él = this.élek elemei:
Ha él.csúcs1 == aktuálisCsúcs:
// Ha ezen a csúcson keresztül szeretnénk
// a másik csúcsba eljutni, akkor
// ennyibe kerülne:
újKöltség = csúcsAdatok[vizsgáltCsúcs].költség + él.súly
// Ha az kisebb, akkor javítsunk az úton:
Ha újKöltség < csúcsAdatok[él.csúcs2].költség:
csúcsAdatok[él.csúcs2].költség = újKöltség
csúcsAdatok[él.csúcs2].forrásCsúcs = aktuálisCsúcs
// Ha minden elemet megvizsgáltunk, akkor a szótárunk
// tartalmazni fogja a költségeket és az útvonalat
vissza: csúcsAdatok
// A következő csúcs a legkisebb költségű lesz,
// de csak akkor, ha még nem vizsgáltuk
Gráf.KövetkezőCsúcs(csúcsAdatok): egész
minIndex = index:
Ciklus index, adat = csúcsAdatok elemei:
Ha (adat.vizsgáltuk == hamis) és
(adat.költség < csúcsAdatok[minIndex].költség):
minIndex = index
vissza: minIndex
</pre>
<h3 id="példa">Példa</h3>
<p>Vegyük az alábbi gráfot:</p>
<p><img src="/assets/img/graph-dijkstra.svg" alt="Gráf példa Dijkstra algoritmusra" /></p>
<p>Az algoritmus végeredménye egy ehhez hasonló táblázat lesz, a kiindulópont itt az 1-es index volt:</p>
<table>
<thead>
<tr>
<th style="text-align: center">kulcs</th>
<th style="text-align: center">forrásCsúcs</th>
<th style="text-align: center">költség</th>
<th style="text-align: center">vizsgáltuk</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">0</td>
<td style="text-align: center">1</td>
<td style="text-align: center">1</td>
<td style="text-align: center">igaz</td>
</tr>
<tr>
<td style="text-align: center">1</td>
<td style="text-align: center">-1</td>
<td style="text-align: center">0</td>
<td style="text-align: center">igaz</td>
</tr>
<tr>
<td style="text-align: center">2</td>
<td style="text-align: center">3</td>
<td style="text-align: center">7</td>
<td style="text-align: center">igaz</td>
</tr>
<tr>
<td style="text-align: center">3</td>
<td style="text-align: center">1</td>
<td style="text-align: center">5</td>
<td style="text-align: center">igaz</td>
</tr>
<tr>
<td style="text-align: center">4</td>
<td style="text-align: center">2</td>
<td style="text-align: center">11</td>
<td style="text-align: center">igaz</td>
</tr>
</tbody>
</table>
<p>Az első oszlop a szótár kulcsa, a többi az objektum értékei.</p>
<p>A forrásCsúcs oszlopot visszakövetve kinyerhetjük, hogy milyen úton jutottunk el pl. a 4-es pontba:</p>
<ul>
<li>A 4 a célpont: [<strong>4</strong>]</li>
<li>4-nek a forráscsúcsa 2: [<strong>2</strong>, 4]</li>
<li>2-nek a forráscsúcsa 3: [<strong>3</strong>, 2, 4]</li>
<li>3-nak a forráscsúcsa 1: [<strong>1</strong>, 3, 2, 4]</li>
<li>Az 1 a kezdőpont, végeztünk</li>
</ul>
<p>Az 1-ből 4-be a legkisebb költségű út az 1-3-2-4 élsorozat, a költsége 11.</p>
<p>Bármely útkeresési algoritmus <strong>csak pozitív súlyokkal működik helyesen.</strong> Ha negatív súlyt is felvennénk, akkor az adott élen oda-vissza járva tetszőlegesen le lehetne csökkenteni a költséget, aminek nincs gyakorlati értelme.</p>A gráfokon különféle algoritmusokat végezhetünk el, ehhez pedig először egy konkrét, objektum-orientált adatszerkezetet kell definiálnunk:Gráfok2018-12-18T10:00:00+01:002018-12-18T10:00:00+01:00/2018/12/18/graf-bevezeto<p>A gráf egy olyan adatszerkezet, amely csúcsokból/pontokból, és azokat összekötő élekből áll.</p>
<p>Ez pl. egy gráf:</p>
<p><img src="/assets/img/graf-pelda.svg" alt="Gráf" /></p>
<p>A gráfokat tipikusan olyan helyzetekben alkalmazzuk, amikor különféle dolgok kapcsolatáról beszélünk.</p>
<p>Ez lehet egy metróhálózat:</p>
<p><img src="/assets/img/graf-metro.svg" alt="Gráf metró" /></p>
<p>Vagy diákok, akiknek legjobb barátaik vannak:</p>
<p><img src="/assets/img/graf-barat.svg" alt="Gráf legjobb barátok" /></p>
<h2 id="néhány-hasznos-kifejezés-szakszó">Néhány hasznos kifejezés, szakszó</h2>
<p>A gráf <strong>összefüggő</strong>, ha bármely pontjából bármely pontjába el lehet jutni.</p>
<p>Ez pl. egy nem összefüggő gráf:</p>
<p><img src="/assets/img/graf-osszefuggetlen.svg" alt="Nem összefüggő gráf" /></p>
<p>A gráf <strong>irányított</strong>, ha ez éleknek van egy kiinduló és egy végpontja. A fenti <em>legjobb barát</em> gráf irányított, míg a <em>metróhálózat</em> irányítatlan.</p>
<p>Egy gráf pedig <strong>súlyozott</strong>, ha az élekhez egy-egy számértéket rendelünk. A <em>metróhálózat</em> gráf súlyozott, míg a <em>legjobb barát</em> súly nélküli.</p>
<p>Egy gráf <strong>egyszerű</strong>, ha nincsenek benne olyan élek, amiknek:</p>
<ul>
<li>Ugyanazt a két pontot kötik össze (többszörös él)</li>
<li>A kezdő- és a végpontja megegyezik (hurokél)</li>
</ul>
<p>A korábbi példák mind egyszerű gráfok, az alábbi viszont nem:</p>
<p><img src="/assets/img/graf-nemegyszeru.svg" alt="Nem egyszeru gráf" /></p>
<p>Egy gráf pedig <strong>fa</strong>, ha összefüggő, és nincs benne kör - azaz ha egy pontból elindulunk, nem lehet visszajutni anélkül, hogy élen kétszer átmennénk. Érdemes összevetni az <a href="/2018/12/02/fa.html">adatszerkezeteknél tanult definícióval</a> - a kettő végeredményben ugyanazt jelenti, de a megközelítés, a felhasználás módja különbözik. E legelső példa az nem fa, mert tartalmazza az “1-2-3” pontokból álló kört.</p>
<p>Sok más hasznos tulajdonságot is elmondhatunk a gráfokról, itt csak a legfontosabbakat szedtem össze. <a href="https://hu.wikipedia.org/wiki/Gr%C3%A1felm%C3%A9leti_fogalomt%C3%A1r">Egy teljesebb körű fogalomtár megtalálható itt.</a></p>
<h2 id="tárolási-mód">Tárolási mód</h2>
<p>A gráfokat természetesen el kell tárolni valahogy, hogy a számítógép dolgozni tudjon vele. Erre a két gyakran alkalmazott megoldási mód van: a csúcsmátrix és az éllista. Vegyük pl. az első példabeli gráfot:</p>
<p><img src="/assets/img/graf-pelda.svg" alt="Gráf" /></p>
<h3 id="csúcsmátrix">Csúcsmátrix</h3>
<p>Ez egy táblázat, ahol a sorok/oszlopok egy-egy csúcst jelképeznek. A táblázatban 1 szerepel, ha az adott élt összeköti egy él, és 0 (vagy üresen is hagyhatjuk), ha nem.</p>
<table>
<thead>
<tr>
<th style="text-align: center"> </th>
<th style="text-align: center">1</th>
<th style="text-align: center">2</th>
<th style="text-align: center">3</th>
<th style="text-align: center">4</th>
<th style="text-align: center">5</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><strong>1</strong></td>
<td style="text-align: center"> </td>
<td style="text-align: center">1</td>
<td style="text-align: center">1</td>
<td style="text-align: center">1</td>
<td style="text-align: center">1</td>
</tr>
<tr>
<td style="text-align: center"><strong>2</strong></td>
<td style="text-align: center">1</td>
<td style="text-align: center"> </td>
<td style="text-align: center">1</td>
<td style="text-align: center"> </td>
<td style="text-align: center"> </td>
</tr>
<tr>
<td style="text-align: center"><strong>3</strong></td>
<td style="text-align: center">1</td>
<td style="text-align: center">1</td>
<td style="text-align: center"> </td>
<td style="text-align: center"> </td>
<td style="text-align: center"> </td>
</tr>
<tr>
<td style="text-align: center"><strong>4</strong></td>
<td style="text-align: center">1</td>
<td style="text-align: center"> </td>
<td style="text-align: center"> </td>
<td style="text-align: center"> </td>
<td style="text-align: center"> </td>
</tr>
<tr>
<td style="text-align: center"><strong>5</strong></td>
<td style="text-align: center">1</td>
<td style="text-align: center"> </td>
<td style="text-align: center"> </td>
<td style="text-align: center"> </td>
<td style="text-align: center"> </td>
</tr>
</tbody>
</table>
<p>Leolvasható, hogy az 1-3 pontokat összeköti egy él, mert a csúcsmátrixban az 1. sor 3. oszlopban szerepel egy 1-es. A 2-4 pontokat viszon nem köti össze, mert a 2. sor 4. oszlopában nincs 1-es.</p>
<h3 id="éllista">Éllista</h3>
<p>Szimplán felsoroljuk az éleket:</p>
<ul>
<li>1-2</li>
<li>1-3</li>
<li>1-4</li>
<li>1-5</li>
<li>2-3</li>
</ul>A gráf egy olyan adatszerkezet, amely csúcsokból/pontokból, és azokat összekötő élekből áll.GitHub - fork-olás2018-12-15T10:00:00+01:002018-12-15T10:00:00+01:00/2018/12/15/github-forking<p>Nyílt forráskódú projekt esetében előfordul, hogy más kódjához mi magunk is hozzá szeretnénk nyúlni, szeretnénk bele fejleszteni, ki szeretnénk egészíteni.
Érthető okokból azonban a projektek tulajdonosai nem adnak bárkinek hozzáférést a repóhoz. Emiatt szükség lesz egy saját változatra.</p>
<p>Megtehetjük azt, hogy saját magunk leklónozzuk a projektet, majd módosítva a “remote” beállításokat egy saját, üres repóba feltöltjük.
Ezt hívjuk a repó <strong>fork</strong>-olásának (a szó azt jelenti, hogy elágazás: ahogy az út egyből kettéágazik, vagy ahogy a villa egy nyeléből több kisebb “ág” nő ki).</p>
<p>Ha a platform megengedi, akkor ezt egy kattintással is elvégezhetjük. A példában a <a href="https://github.com">GitHub</a> weboldalt használom.</p>
<h2 id="forkolás">Forkolás</h2>
<p>A projekt weboldalán kattintsunk erre a gombra:</p>
<p><img src="/assets/img/github_fork.png" alt="Fork" /></p>
<p>Ez után a saját projektjeink között meg fog jelenni a repó, együtt a teljes történelmével, változtatásaival.</p>
<p>A kapcsolat nem szűnik meg a két repó között, a név alatt látszik, hogy melyik volt az eredeti repó:</p>
<p><img src="/assets/img/github_forked.png" alt="Fork-olt projekt" /></p>
<p>Forkolás után a két projektet különbözőnek lehet tekinteni, a saját repónkba gond nélkül tudunk saját kódot push-olni - de ha szeretnénk, akkor az eredetit is nyomon tudjuk követni, néhány parancs kiadásával az eredeti projekt változtatásait is tudjuk integrálni.</p>
<p>Ilyen módon lehet karban tartani pl. egy népszerű, Linux-os projekt Windows-os változatát, ha az eredeti készítő erre nem tud időt szánni:</p>
<ul>
<li>Az eredeti projekt a Linux-os kódot tartalmazza</li>
<li>A forkolt projekt tartalmazza a szükséges Windows-os módosításokat, miközben naprakészen tud maradni az eredetivel</li>
</ul>
<p>A két projekt innentől kezdve párhuzamosan fut.</p>
<p>Alkalomszerűen előfordulhat, hogy a fork-olt projekt válik népszerűbbé, mert az eredeti tulajdonosánál jobban karban tartja, gyorsabban fejleszti az új funkciókat stb. Ez történt az OpenOffice esetében is: az eredeti projekt jövőjét féltették az Oracle-től, ezért készítettek a fork-ot, a jogi problémákat elkerülendő a projektet átneveztét LibreOffice-re, és azóta ez lett a népszerűbb változat.</p>
<h2 id="pull-request">Pull request</h2>
<p>Ha a saját módosításunkat az eredeti projekt fejlesztői is szívesen látnának, akkor készíthetünk egy ún. <strong>pull request</strong>-et. Ez egy felkérés arra, hogy bizonyos commitokat az eredeti fejlesztő pull-oljon a mi projektünkből. Ezt ő pedig elfogadhatja, vagy visszautasíthatja.</p>
<p><a href="https://github.com/dotnet/roslyn/pull/31801">Példa egy pull request-re</a>: lehet comment-elni, látszanak a bele tartozó commitok.</p>
<p>Github-on a pull request az elsődleges módszer a különböző fejlesztők közös munkájához. Cégen belüli, privát git repók esetében is léteznek hasonló funkciók, amely egy repón belül, a különböző fejlesztési ágak között valósítja meg ugyanezt (ún. <strong>merge request</strong> - merge-elésre felkérés).</p>Nyílt forráskódú projekt esetében előfordul, hogy más kódjához mi magunk is hozzá szeretnénk nyúlni, szeretnénk bele fejleszteni, ki szeretnénk egészíteni. Érthető okokból azonban a projektek tulajdonosai nem adnak bárkinek hozzáférést a repóhoz. Emiatt szükség lesz egy saját változatra.