Telegram-bot vir 'n persoonlike keuse van artikels van Habr

Vir vrae soos "hoekom?" daar is 'n ouer artikel - Natural Geektimes - maak ruimte skoner.

Daar is baie artikels, om subjektiewe redes hou ek nie van hulle nie, en sommige, inteendeel, dit is jammer om oor te slaan. Ek wil graag hierdie proses optimaliseer en tyd bespaar.

Die bogenoemde artikel het 'n scriptbenadering in die blaaier voorgestel, maar ek het nie regtig daarvan gehou nie (al het ek dit al voorheen gebruik) om die volgende redes:

  • Vir verskillende blaaiers op jou rekenaar/foon moet jy dit weer opstel, indien enigsins moontlik.
  • Streng filtering deur skrywers is nie altyd gerieflik nie.
  • Die probleem met skrywers wie se artikels jy nie wil mis nie, al word dit een keer per jaar gepubliseer, is nie opgelos nie.

Filtrering wat in die webwerf ingebou is op grond van artikelgraderings is nie altyd gerieflik nie, aangesien hoogs gespesialiseerde artikels, ten spyte van hul waarde, 'n taamlik beskeie gradering kan ontvang.

Aanvanklik wou ek 'n RSS-feed (of selfs verskeie) genereer, en net interessante dinge daar laat. Maar op die ou end het dit geblyk dat die lees van RSS nie baie gerieflik gelyk het nie: in elk geval, om kommentaar te lewer/vir 'n artikel te stem/dit by jou gunstelinge te voeg, moet jy deur die blaaier gaan. Daarom het ek 'n telegrambot geskryf wat interessante artikels vir my in 'n persoonlike boodskap stuur. Telegram maak self pragtige voorskoue daarvan, wat gekombineer met inligting oor die skrywer/gradering/kyke nogal insiggewend lyk.

Telegram-bot vir 'n persoonlike keuse van artikels van Habr

Onder die snit is besonderhede soos die kenmerke van die werk, die skryfproses en tegniese oplossings.

Kortliks oor die bot

Bewaarplek: https://github.com/Kright/habrahabr_reader

Bot in telegram: https://t.me/HabraFilterBot

Die gebruiker stel 'n bykomende gradering vir merkers en outeurs. Daarna word 'n filter op die artikels toegepas - die artikel se gradering op Habré, die skrywer se gebruikersgradering en die gemiddelde vir gebruikergraderings volgens merker word opgetel. As die hoeveelheid groter is as 'n gebruiker-gespesifiseerde drempel, dan slaag die artikel die filter.

'n Sydoelwit van die skryf van 'n bot was om pret en ervaring op te doen. Daarby het ek myself gereeld daaraan herinner Ek is nie Google nie, en daarom word baie dinge so eenvoudig en selfs primitief moontlik gedoen. Dit het egter nie verhoed dat die proses om die bot te skryf drie maande duur nie.

Dit was somer buite

Julie was verby, en ek het besluit om 'n bot te skryf. En nie alleen nie, maar saam met 'n vriend wat scala bemeester het en iets daaroor wou skryf. Die begin het belowend gelyk - die kode sou deur 'n span gesny word, die taak het maklik gelyk en ek het gedink dat die bot oor 'n paar weke of 'n maand gereed sou wees.

Ten spyte van die feit dat ek self die laaste paar jaar van tyd tot tyd kode op die rots skryf, sien of kyk niemand gewoonlik hierdie kode nie: troeteldierprojekte, toets van idees, voorafverwerking van data, bemeestering van sommige konsepte van FP. Ek was baie geïnteresseerd in hoe die skryf van kode in 'n span lyk, want kode op rots kan op baie verskillende maniere geskryf word.

Wat kon gegaan het so? Laat ons egter nie dinge haas nie.
Alles wat gebeur kan opgespoor word deur die pleeggeskiedenis te gebruik.

’n Kennis het op 27 Julie ’n bewaarplek geskep, maar niks anders gedoen nie, toe begin ek kode skryf.

30 Julie

Kortliks: Ek het 'n ontleding van Habr se rss-stroom geskryf.

  • com.github.pureconfig vir die lees van tipeveilige konfigurasies direk in gevalklasse (dit blyk baie gerieflik te wees)
  • scala-xml vir die lees van xml: aangesien ek aanvanklik my eie implementering vir die rss-feed wou skryf, en die rss-feed is in xml-formaat, het ek hierdie biblioteek vir ontleding gebruik. Eintlik het RSS-ontleding ook verskyn.
  • scalatest vir toetse. Selfs vir klein projekte spaar die skryf van toetse tyd - byvoorbeeld, wanneer u xml-ontleding ontfout, is dit baie makliker om dit na 'n lêer af te laai, toetse te skryf en foute reg te stel. Toe 'n fout later verskyn met die ontleding van 'n vreemde html met ongeldige utf-8-karakters, was dit geriefliker om dit in 'n lêer te plaas en 'n toets by te voeg.
  • akteurs van Akka. Objektief was hulle glad nie nodig nie, maar die projek is vir die pret geskryf, ek wou hulle probeer. Gevolglik is ek gereed om te sê dat ek daarvan gehou het. Die idee van OOP kan van die ander kant af gekyk word - daar is akteurs wat boodskappe uitruil. Wat meer interessant is, is dat jy kode op so 'n manier kan (en moet) skryf dat die boodskap dalk nie opdaag nie of dalk nie verwerk word nie (oor die algemeen, wanneer die rekening op een enkele rekenaar loop, moet boodskappe nie verlore gaan nie). Ek het eers my kop gekrap en daar was gemors in die kode met akteurs wat op mekaar ingeteken het, maar op die ou end het ek daarin geslaag om met 'n redelik eenvoudige en elegante argitektuur vorendag te kom. Die kode binne elke akteur kan as enkeldraad beskou word; wanneer 'n akteur ineenstort, begin die acca dit weer - die resultaat is 'n redelik foutverdraagsame stelsel.

9 Augustus

Ek het by die projek gevoeg scala-scrapper vir die ontleding van HTML-bladsye vanaf Habr (om inligting soos artikelgradering, aantal boekmerke, ens.) uit te haal.

En Katte. Die in die rots.

Telegram-bot vir 'n persoonlike keuse van artikels van Habr

Ek lees toe 'n boek oor verspreide databasisse, ek hou van die idee van CRDT (Konflikvrye gerepliseerde datatipe, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, habr), so ek het 'n tipe klas van 'n kommutatiewe semigroep geplaas vir inligting oor die artikel oor Habré.

Trouens, die idee is baie eenvoudig – ons het tellers wat eentonig verander. Die aantal promosies groei geleidelik, asook die aantal pluspunte (sowel as die aantal minusse). As ek twee weergawes van inligting oor 'n artikel het, kan ek dit "in een saamsmelt" - die toestand van die toonbank wat groter is, word as meer relevant beskou.

'n Halfgroep beteken dat twee voorwerpe met inligting oor 'n artikel in een saamgevoeg kan word. Kommutatief beteken dat jy beide A + B en B + A kan saamsmelt, die resultaat hang nie af van die volgorde nie, en uiteindelik sal die nuutste weergawe bly. Terloops, hier is ook assosiatiwiteit.

Byvoorbeeld, soos beplan, het rss na ontleding effens verswakte inligting oor die artikel verskaf - sonder maatstawwe soos die aantal kyke. ’n Spesiale akteur het toe inligting oor die artikels geneem en na die html-bladsye gehardloop om dit by te werk en met die ou weergawe saam te voeg.

Oor die algemeen, soos in akka, was dit nie nodig nie, jy kon eenvoudig updateDate vir die artikel stoor en 'n nuwer een neem sonder enige samesmeltings, maar die pad van avontuur het my gelei.

12 Augustus

Ek het vryer begin voel en net vir die pret het ek elke klets 'n aparte akteur gemaak. Teoreties weeg 'n akteur self ongeveer 300 grepe en hulle kan in miljoene geskep word, so dit is 'n heeltemal normale benadering. Dit lyk vir my dat die oplossing baie interessant geblyk het:

Een akteur was 'n brug tussen die telegrambediener en die boodskapstelsel in Akka. Hy het bloot boodskappe ontvang en dit aan die verlangde kletsakteur gestuur. Die kletsakteur kon in reaksie iets terugstuur – en dit sou na die telegram teruggestuur word. Wat baie gerieflik was, is dat hierdie akteur so eenvoudig as moontlik geblyk het en slegs die logika bevat het om op boodskappe te reageer. Terloops, inligting oor nuwe artikels het by elke klets gekom, maar ek sien weereens geen probleme hierin nie.

Oor die algemeen het die bot reeds gewerk, op boodskappe gereageer, 'n lys artikels gestoor wat aan die gebruiker gestuur is, en ek het al gedink dat die bot amper gereed was. Ek het stadig klein kenmerke bygevoeg soos die normalisering van skrywersname en -etikette (vervang "sd f" met "s_d_f").

Daar was net een ding oor klein maar — die staat is nêrens gered nie.

Alles het verkeerd geloop

Jy het dalk opgemerk dat ek die bot meestal alleen geskryf het. Dus, die tweede deelnemer het by die ontwikkeling betrokke geraak, en die volgende veranderinge het in die kode verskyn:

  • MongoDB blyk te stoor staat. Terselfdertyd is die logs in die projek gebreek, want om een ​​of ander rede het Monga hulle begin strooipos en sommige mense het dit eenvoudig wêreldwyd afgeskakel.
  • Die brug-akteur in Telegram is onherkenbaar getransformeer en het self boodskappe begin ontleed.
  • Akteurs vir geselsies is genadeloos uitgesny, en in plaas daarvan is hulle vervang deur 'n akteur wat al die inligting oor alle geselsies op een slag weggesteek het. Vir elke nies het dié akteur in die moeilikheid beland. Wel, ja, soos wanneer inligting oor 'n artikel opgedateer word, is dit moeilik om dit aan alle kletsakteurs te stuur (ons is soos Google, miljoene gebruikers wag vir 'n miljoen artikels in die klets vir elkeen), maar elke keer as die klets opgedateer word, dit is normaal om na Monga te gaan. Soos ek heelwat later besef het, was die werklogika van die geselsies ook heeltemal uitgesny en in die plek daarvan het iets verskyn wat nie werk nie.
  • Daar is geen spoor oor van die tipe klasse nie.
  • Een of ander ongesonde logika het in die akteurs verskyn met hul intekeninge op mekaar, wat tot 'n rastoestand gelei het.
  • Datastrukture met tipe velde Option[Int] verander in Int met magiese verstekwaardes soos -1. Later het ek besef dat mongoDB json stoor en daar is niks fout daarmee om dit daar te stoor nie Option wel, of ten minste ontleed -1 as Geen, maar op daardie stadium het ek dit nie geweet nie en het my woord daarvoor aanvaar dat "dit is hoe dit moet wees." Ek het nie daardie kode geskryf nie, en ek het nie die moeite gedoen om dit vir eers te verander nie.
  • Ek het uitgevind dat my openbare IP-adres geneig is om te verander, en elke keer moes ek dit by die witlys van Mongo voeg. Ek het die bot plaaslik bekendgestel, Monga was iewers op die bedieners van Monga as 'n maatskappy.
  • Skielik het die normalisering van etikette en boodskapformatering vir telegramme verdwyn. (Hmm, hoekom sou dit wees?)
  • Ek het daarvan gehou dat die bot se toestand in 'n eksterne databasis gestoor word, en wanneer dit weer begin word, gaan dit voort om te werk asof niks gebeur het nie. Dit was egter die enigste pluspunt.

Die tweede persoon was nie besonder haastig nie, en al hierdie veranderinge het reeds aan die begin van September in een groot hoop verskyn. Ek het nie dadelik die omvang van die gevolglike vernietiging waardeer nie en het die werk van die databasis begin verstaan, want ... Ek het nog nooit voorheen met hulle te doen gehad nie. Eers later het ek besef hoeveel werkkode gesny is en hoeveel foute in die plek daarvan bygevoeg is.

September

Ek het eers gedink dit sal nuttig wees om Monga te bemeester en dit goed te doen. Toe begin ek stadigaan verstaan ​​dat die organisering van kommunikasie met die databasis ook 'n kuns is waarin jy baie wedrenne kan maak en net foute kan maak. Byvoorbeeld, as die gebruiker twee boodskappe soos /subscribe - en in reaksie op elkeen sal ons 'n inskrywing in die tabel skep, want ten tyde van die verwerking van daardie boodskappe is die gebruiker nie ingeteken nie. Ek het 'n vermoede dat kommunikasie met Monga in sy huidige vorm nie op die beste manier geskryf is nie. Die gebruiker se instellings is byvoorbeeld geskep op die oomblik dat hy ingeteken het. As hy probeer het om hulle te verander voor die feit van inskrywing ... die bot het niks gereageer nie, want die kode in die akteur het in die databasis vir die instellings ingegaan, dit nie gevind nie en het neergestort. Toe ek gevra is hoekom nie instellings skep soos nodig nie, het ek geleer dat dit nie nodig is om dit te verander as die gebruiker nie ingeteken het nie... Die boodskapfilterstelsel is op een of ander manier nie-natuurlik gemaak nie, en selfs na 'n noukeurige kyk na die kode kon ek verstaan ​​nie of dit aanvanklik so bedoel was of daar 'n fout daar is nie.

Daar was geen lys van artikels wat by die klets ingedien is nie; daar is eerder voorgestel dat ek dit self skryf. Dit het my verbaas – oor die algemeen was ek nie daarteen om allerhande goed by die projek in te sleep nie, maar dit sou logies wees vir die een wat hierdie goed ingebring en vasgeskroef het. Maar nee, dit het gelyk of die tweede deelnemer alles opgegee het, maar het gesê dat die lys binne die klets kwansuis 'n slegte oplossing was, en dit was nodig om 'n bord te maak met gebeurtenisse soos "'n artikel y is na gebruiker x gestuur." Dan, as die gebruiker versoek het om nuwe artikels te stuur, was dit nodig om 'n versoek na die databasis te stuur, wat gebeurtenisse wat verband hou met die gebruiker uit die gebeurtenisse sou kies, ook 'n lys van nuwe artikels sou kry, dit filter, dit aan die gebruiker stuur en gooi gebeure hieroor terug in die databasis.

Die tweede deelnemer is iewers weggevoer na abstraksies, wanneer die bot nie net artikels van Habr sal ontvang nie en nie net na telegram gestuur word nie.

Ek het op een of ander manier gebeure geïmplementeer in die vorm van 'n aparte teken vir die tweede helfte van September. Dit is nie optimaal nie, maar ten minste het die bot begin werk en weer vir my artikels begin stuur, en ek het stadig uitgevind wat in die kode gebeur.

Nou kan jy teruggaan na die begin en onthou dat die bewaarplek nie oorspronklik deur my geskep is nie. Wat kon so verloop het? My trekversoek is afgekeur. Dit het geblyk dat ek redneck-kode gehad het, dat ek nie geweet het hoe om in 'n span te werk nie, en ek moes foute in die huidige implementeringskromme regmaak en dit nie verfyn na 'n bruikbare toestand nie.

Ek het ontsteld geraak en gekyk na die pleeggeskiedenis en die hoeveelheid kode wat geskryf is. Ek het gekyk na oomblikke wat oorspronklik goed geskryf is, en toe teruggebreek is ...

F*rk dit

Ek het die artikel onthou Jy is nie Google nie.

Ek het gedink dat niemand regtig 'n idee nodig het sonder implementering nie. Ek het gedink dat ek 'n werkende bot wil hê, wat in een enkele kopie op een enkele rekenaar sal werk as 'n eenvoudige Java-program. Ek weet dat my bot vir maande sonder herstart sal werk, aangesien ek al sulke bots in die verlede geskryf het. As dit skielik val en nie die gebruiker nog 'n artikel stuur nie, sal die lug nie op die grond val nie en niks katastrofies sal gebeur nie.

Hoekom het ek Docker, mongoDB en ander vragkultus van "ernstige" sagteware nodig as die kode eenvoudig nie werk nie of skeef werk?

Ek het die projek gevurk en alles gedoen soos ek wou.

Telegram-bot vir 'n persoonlike keuse van artikels van Habr

Omtrent dieselfde tyd het ek van werk verander en vrye tyd het grootliks ontbreek. Soggens het ek reg op die trein wakker geword, saans het ek laat teruggekeer en wou niks meer doen nie. Ek het vir 'n rukkie niks gedoen nie, toe het die begeerte om die bot klaar te maak my oorweldig, en ek het die kode stadig begin herskryf terwyl ek soggens werk toe ry. Ek sal nie sê dat dit produktief was nie: om op 'n bewende trein met 'n skootrekenaar op jou skoot te sit en na die oorloop van jou foon te kyk, is nie baie gerieflik nie. Die tyd wat spandeer is om kode te skryf, het egter heeltemal ongemerk verbygevlieg, en die projek het stadig begin beweeg na 'n werkende toestand.

Iewers in my agterkop was daar 'n wurm van twyfel wat mongoDB wou gebruik, maar ek het gedink dat daar benewens die voordele van "betroubare" staatberging ook merkbare nadele was:

  • Die databasis word nog 'n punt van mislukking.
  • Die kode word meer kompleks, en dit sal my langer neem om dit te skryf.
  • Die kode word stadig en ondoeltreffend; in plaas daarvan om 'n voorwerp in die geheue te verander, word die veranderinge na die databasis gestuur en, indien nodig, teruggetrek.
  • Daar is beperkings op die tipe berging van gebeure in 'n aparte tabel, wat verband hou met die eienaardighede van die databasis.
  • Die proefweergawe van Monga het 'n paar beperkings, en as jy dit raakloop, sal jy Monga op iets moet begin en opstel.

Ek sny die monga uit, nou word die bot se toestand eenvoudig in die program se geheue gestoor en van tyd tot tyd in 'n lêer in die vorm van json gestoor. Miskien sal hulle in die kommentaar skryf dat ek verkeerd is, dat dit is waar die databasis gebruik moet word, ens. Maar dit is my projek, die benadering met die lêer is so eenvoudig as moontlik en dit werk op 'n deursigtige manier.

Het magiese waardes soos -1 uitgegooi en normale teruggestuur Option, bygevoeg berging van 'n hash tabel met gestuur artikels terug na die voorwerp met klets inligting. Bygevoeg verwydering van inligting oor artikels ouer as vyf dae, om nie alles te stoor nie. Ek het logboek na 'n werkende toestand gebring - logs word in redelike hoeveelhede na beide die lêer en die konsole geskryf. Verskeie admin-opdragte bygevoeg soos die stoor van staat of die verkryging van statistieke soos die aantal gebruikers en artikels.

Het 'n klomp klein dingetjies reggemaak: byvoorbeeld, vir artikels word die aantal kyke, laaiks, laaiks en opmerkings nou aangedui ten tyde van die verbygaan van die gebruiker se filter. Oor die algemeen is dit verbasend hoeveel klein dingetjies reggestel moes word. Ek het 'n lys gehou, al die "onreëlmatighede" daar opgemerk en dit so ver moontlik reggestel.

Ek het byvoorbeeld die vermoë bygevoeg om alle instellings direk in een boodskap te stel:

/subscribe
/rating +20
/author a -30
/author s -20
/author p +9000
/tag scala 20
/tag akka 50

En nog 'n span /settings vertoon hulle presies in hierdie vorm, kan jy die teks daaruit neem en al die instellings aan 'n vriend stuur.
Dit lyk na 'n klein dingetjie, maar daar is dosyne soortgelyke nuanses.

Geïmplementeerde artikelfiltrering in die vorm van 'n eenvoudige lineêre model - die gebruiker kan 'n bykomende gradering vir outeurs en etikette stel, sowel as 'n drempelwaarde. As die som van die skrywer se gradering, die gemiddelde gradering vir etikette en die werklike gradering van die artikel groter is as die drempelwaarde, dan word die artikel aan die gebruiker gewys. Jy kan óf die bot vra vir artikels met die opdrag /new, óf op die bot inteken en dit sal enige tyd van die dag artikels in 'n persoonlike boodskap stuur.

Oor die algemeen het ek 'n idee gehad vir elke artikel om meer kenmerke uit te haal (hubs, aantal opmerkings, boekmerke, dinamika van graderingsveranderinge, hoeveelheid teks, prente en kode in die artikel, sleutelwoorde), en die gebruiker 'n ok/ nie ok stem onder elke artikel en lei 'n model vir elke gebruiker op nie, maar ek was te lui.

Boonop sal die logika van die werk nie so voor die hand liggend wees nie. Nou kan ek handmatig 'n gradering van +9000 vir patientZero stel en met 'n drempelgradering van +20 sal ek gewaarborg wees om al sy artikels te ontvang (tensy ek natuurlik -100500 vir sommige etikette stel).

Die finale argitektuur blyk redelik eenvoudig te wees:

  1. 'n Akteur wat die toestand van alle geselsies en artikels stoor. Dit laai sy toestand vanaf 'n lêer op die skyf en stoor dit van tyd tot tyd terug, elke keer na 'n nuwe lêer.
  2. 'n Akteur wat die RSS-stroom van tyd tot tyd besoek, van nuwe artikels leer, na die skakels kyk, ontleed en hierdie artikels na die eerste akteur stuur. Boonop vra dit soms 'n lys artikels van die eerste akteur, kies dié wat nie ouer as drie dae is nie, maar lanklaas opgedateer is, en dateer dit op.
  3. ’n Akteur wat met ’n telegram kommunikeer. Ek het nog steeds die boodskap ontleed heeltemal hierheen gebring. Op 'n vriendskaplike manier wil ek dit in twee verdeel - sodat een inkomende boodskappe ontleed, en die tweede handel oor vervoerprobleme soos om ongestuurde boodskappe weer te stuur. Nou is daar geen herstuur nie, en 'n boodskap wat weens 'n fout nie opgedaag het nie, sal eenvoudig verlore gaan (tensy dit in die logs aangeteken word), maar tot dusver het dit geen probleme veroorsaak nie. Miskien sal probleme ontstaan ​​as 'n klomp mense op die bot inteken en ek die limiet bereik vir die stuur van boodskappe).

Waarvan ek gehou het, is dat danksy akka, val van akteurs 2 en 3 oor die algemeen nie die prestasie van die bot beïnvloed nie. Miskien word sommige artikels nie betyds opgedateer nie of sommige boodskappe bereik nie die telegram nie, maar die rekening begin die akteur weer en alles gaan voort om te werk. Ek stoor die inligting dat die artikel aan die gebruiker gewys word eers wanneer die telegramakteur reageer dat hy die boodskap suksesvol afgelewer het. Die ergste ding wat my dreig is om die boodskap verskeie kere te stuur (as dit afgelewer word, maar die bevestiging is op een of ander manier verlore). In beginsel, as die eerste akteur nie die staat in homself gestoor het nie, maar met een of ander databasis gekommunikeer het, dan kan hy ook onmerkbaar val en na die lewe terugkeer. Ek kan ook akka volharding probeer om die toestand van akteurs te herstel, maar die huidige implementering pas my met sy eenvoud. Dit is nie dat my kode gereeld neergestort het nie – inteendeel, ek het nogal baie moeite gedoen om dit onmoontlik te maak. Maar kak gebeur, en die vermoë om die program in geïsoleerde stukke op te breek-akteurs het vir my baie gerieflik en prakties gelyk.

Ek het sirkel-ci bygevoeg sodat as die kode breek, jy dadelik daarvan sal uitvind. Op 'n minimum beteken dit dat die kode opgehou het om saam te stel. Ek wou aanvanklik travis byvoeg, maar dit het net my projekte sonder vurke gewys. Oor die algemeen kan albei hierdie dinge vrylik in oop bewaarplekke gebruik word.

Resultate van

Dis al November. Die bot is geskryf, ek gebruik dit die afgelope twee weke en ek het daarvan gehou. As jy idees vir verbetering het, skryf. Ek sien nie die nut daarin om dit te verdien nie - laat dit net werk en stuur interessante artikels.

Bot skakel: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Klein gevolgtrekkings:

  • Selfs 'n klein projek kan baie tyd neem.
  • Jy is nie Google nie. Daar is geen sin om mossies uit 'n kanon te skiet nie. 'n Eenvoudige oplossing kan net so goed werk.
  • Troeteldierprojekte is baie goed om met nuwe tegnologieë te eksperimenteer.
  • Telegram-bots word baie eenvoudig geskryf. As dit nie vir "spanwerk" en eksperimente met tegnologie was nie, sou die bot oor 'n week of twee geskryf gewees het.
  • Die akteursmodel is 'n interessante ding wat goed gaan met multi-threading en foutverdraagsame kode.
  • Ek dink ek het 'n voorsmakie gekry van hoekom die oopbrongemeenskap lief is vir vurke.
  • Databasisse is goed omdat die toepassingstatus nie meer van toepassing-ongelukke/herbegin afhanklik is nie, maar om met 'n databasis te werk, kompliseer die kode en stel beperkings op die datastruktuur op.

Bron: will.com

Voeg 'n opmerking