Node.js Geliştiricilerinin Yaptığı En Yaygın 10 Hata

Yayınlanan: 2022-03-11

Node.js dünyaya tanıtıldığı andan itibaren hem övgü hem de eleştiriden adil bir pay aldı. Tartışma hala devam ediyor ve yakın zamanda bitmeyebilir. Bu tartışmalarda sıklıkla gözden kaçırdığımız şey, her programlama dilinin ve platformun, platformu nasıl kullandığımızın yarattığı belirli konulara göre eleştirilmesidir. Node.js'nin güvenli kod yazmayı ne kadar zorlaştırdığına ve son derece eşzamanlı kod yazmayı ne kadar kolaylaştırdığına bakılmaksızın, platform bir süredir var ve çok sayıda sağlam ve karmaşık web hizmeti oluşturmak için kullanılıyor. Bu web servisleri iyi bir şekilde ölçeklenir ve kararlılığını İnternetteki zamana dayanıklılıklarıyla kanıtlamıştır.

Ancak, diğer tüm platformlar gibi Node.js de geliştirici sorunlarına ve sorunlarına açıktır. Bu hatalardan bazıları performansı düşürürken, diğerleri Node.js'yi elde etmeye çalıştığınız şey için doğrudan kullanılamaz hale getirir. Bu makalede, Node.js'de yeni olan geliştiricilerin sıklıkla yaptığı on yaygın hataya ve bunların bir Node.js uzmanı olmak için nasıl önlenebileceğine göz atacağız.

node.js geliştirici hataları

Hata 1: Olay döngüsünü engelleme

Node.js'deki JavaScript (tıpkı tarayıcıda olduğu gibi) tek iş parçacıklı bir ortam sağlar. Bu, uygulamanızın hiçbir iki bölümünün paralel olarak çalışmadığı anlamına gelir; bunun yerine, eşzamanlılık, G/Ç'ye bağlı işlemlerin eşzamansız olarak işlenmesi yoluyla elde edilir. Örneğin, Node.js'den veritabanı motoruna bir belge getirme isteği, Node.js'nin uygulamanın başka bir bölümüne odaklanmasını sağlayan şeydir:

 // Trying to fetch an user object from the database. Node.js is free to run other parts of the code from the moment this function is invoked.. db.User.get(userId, function(err, user) { // .. until the moment the user object has been retrieved here }) 

node.js tek iş parçacıklı ortam

Ancak, binlerce istemcinin bağlı olduğu bir Node.js örneğinde CPU'ya bağlı bir kod parçası, olay döngüsünü engellemek ve tüm istemcileri bekletmek için gereken tek şeydir. CPU'ya bağlı kodlar arasında büyük bir diziyi sıralamaya çalışmak, son derece uzun bir döngü çalıştırmak vb. Örneğin:

 function sortUsersByAge(users) { users.sort(function(a, b) { return a.age < b.age ? -1 : 1 }) }

Bu "sortUsersByAge" işlevini çağırmak, küçük bir "kullanıcı" dizisinde çalıştırılıyorsa iyi olabilir, ancak büyük bir diziyle, genel performans üzerinde korkunç bir etkisi olacaktır. Bu kesinlikle yapılması gereken bir şeyse ve olay döngüsünde bekleyen başka bir şey olmayacağından eminseniz (örneğin, bu Node.js ile oluşturduğunuz bir komut satırı aracının parçasıysa ve her şeyin eşzamanlı olarak çalışması önemli değil), o zaman bu bir sorun olmayabilir. Ancak, aynı anda binlerce kullanıcıya hizmet vermeye çalışan bir Node.js sunucu örneğinde, böyle bir kalıp ölümcül olabilir.

Bu kullanıcı dizisi veritabanından alınıyorsa, ideal çözüm, zaten sıralanmış olan diziyi doğrudan veritabanından getirmek olacaktır. Olay döngüsü, uzun bir finansal işlem verisi geçmişinin toplamını hesaplamak için yazılmış bir döngü tarafından bloke edilmişse, olay döngüsünün boşa gitmesini önlemek için bazı harici çalışan/kuyruk kurulumuna ertelenebilir.

Gördüğünüz gibi, bu tür bir Node.js sorununun kesin çözümü yoktur, bunun yerine her vakanın ayrı ayrı ele alınması gerekir. Temel fikir, öne bakan Node.js örneklerinde (istemcilerin aynı anda bağlandığı örnekler) CPU yoğun çalışma yapmamaktır.

Hata #2: Bir Geri Aramayı Birden Fazla Çağırma

JavaScript, sonsuza dek geri aramalara güveniyor. Web tarayıcılarında olaylar, geri aramalar gibi davranan (genellikle anonim) işlevlere referanslar iletilerek işlenir. Node.js'de, sözler verilene kadar kodunuzun asenkron öğelerinin birbirleriyle iletişim kurmasının tek yolu geri aramalardı. Geri aramalar hala kullanılmaktadır ve paket geliştiricileri API'lerini hala geri aramalar etrafında tasarlamaktadır. Geri aramaları kullanmayla ilgili yaygın bir Node.js sorunu, onları bir kereden fazla çağırmaktır. Tipik olarak, bir paket tarafından asenkron olarak bir şey yapmak için sağlanan bir fonksiyon, asenkron görev tamamlandığında çağrılan son argümanı olarak bir fonksiyonu beklemek üzere tasarlanmıştır:

 module.exports.verifyPassword = function(user, password, done) { if(typeof password !== 'string') { done(new Error('password should be a string')) return } computeHash(password, user.passwordHashOpts, function(err, hash) { if(err) { done(err) return } done(null, hash === user.passwordHash) }) }

En son sefere kadar her "done" çağrıldığında bir return ifadesinin nasıl olduğuna dikkat edin. Bunun nedeni, geri aramanın çağrılmasının geçerli işlevin yürütülmesini otomatik olarak sona erdirmemesidir. İlk "dönüş" yorumlandıysa, bu işleve dize olmayan bir parola iletmek yine de "computeHash" öğesinin çağrılmasına neden olur. "computeHash"ın böyle bir senaryoyu nasıl ele aldığına bağlı olarak, "done" birden çok kez çağrılabilir. Bu işlevi başka bir yerden kullanan herkes, geçtikleri geri arama birden çok kez başlatıldığında tamamen hazırlıksız yakalanabilir.

Bu Node.js hatasını önlemek için dikkatli olmak yeterlidir. Bazı Node.js geliştiricileri, her geri arama çağrısından önce bir return anahtar sözcüğü ekleme alışkanlığını benimser:

 if(err) { return done(err) }

Çoğu zaman uyumsuz işlevde, dönüş değerinin neredeyse hiçbir önemi yoktur, bu nedenle bu yaklaşım genellikle böyle bir sorundan kaçınmayı kolaylaştırır.

Hata #3: Derinlemesine Yuvalanmış Geri Aramalar

Sıklıkla "geri arama cehennemi" olarak anılan derinlemesine iç içe geçmiş geri aramalar, kendi başına bir Node.js sorunu değildir. Ancak bu, kodun hızla kontrolden çıkmasına neden olan sorunlara neden olabilir:

 function handleLogin(..., done) { db.User.get(..., function(..., user) { if(!user) { return done(null, 'failed to log in') } utils.verifyPassword(..., function(..., okay) { if(okay) { return done(null, 'failed to log in') } session.login(..., function() { done(null, 'logged in') }) }) }) } 

geri arama cehennemi

Görev ne kadar karmaşıksa, o kadar kötü olabilir. Geri aramaları bu şekilde iç içe yerleştirerek, kolayca hataya açık, okunması zor ve bakımı zor kodlar elde ederiz. Çözümlerden biri, bu görevleri küçük işlevler olarak bildirmek ve ardından bunları birbirine bağlamaktır. Bununla birlikte, bunun (tartışmalı) en temiz çözümlerinden biri, Async.js gibi eşzamansız JavaScript kalıplarıyla ilgilenen bir yardımcı program Node.js paketi kullanmaktır:

 function handleLogin(done) { async.waterfall([ function(done) { db.User.get(..., done) }, function(user, done) { if(!user) { return done(null, 'failed to log in') } utils.verifyPassword(..., function(..., okay) { done(null, user, okay) }) }, function(user, okay, done) { if(okay) { return done(null, 'failed to log in') } session.login(..., function() { done(null, 'logged in') }) } ], function() { // ... }) }

"async.waterfall"a benzer şekilde, Async.js'nin farklı asenkron kalıplarla başa çıkmak için sağladığı bir dizi başka işlev vardır. Kısaca, burada daha basit örnekler kullandık, ancak gerçeklik genellikle daha kötüdür.

Hata #4: Geri Aramaların Eşzamanlı Olarak Çalıştırılmasını Beklemek

Geri aramalarla zaman uyumsuz programlama, JavaScript ve Node.js'ye özgü bir şey olmayabilir, ancak popülaritesinden sorumludurlar. Diğer programlama dillerinde, deyimler arasında geçiş yapmak için özel bir talimat olmadıkça, iki deyimin birbiri ardına yürütüleceği öngörülebilir yürütme sırasına alışığız. O zaman bile, bunlar genellikle koşullu ifadeler, döngü ifadeleri ve işlev çağrılarıyla sınırlıdır.

Bununla birlikte, JavaScript'te, geri aramalarla belirli bir işlev, beklediği görev bitene kadar iyi çalışmayabilir. Geçerli işlevin yürütülmesi, herhangi bir durma olmaksızın sonuna kadar çalışacaktır:

 function testTimeout() { console.log(“Begin”) setTimeout(function() { console.log(“Done!”) }, duration * 1000) console.log(“Waiting..”) }

Fark edeceğiniz gibi, “testTimeout” işlevinin çağrılması, önce “Başla”, ardından “Bekliyor..” ve ardından “Bitti!” mesajını yazdıracaktır. yaklaşık bir saniye sonra.

Bir geri arama başlatıldıktan sonra olması gereken her şey, bunun içinden çağrılmalıdır.

Hata #5: “module.exports” Yerine “exports” a atamak

Node.js, her dosyaya küçük, yalıtılmış bir modül gibi davranır. Paketinizde "a.js" ve "b.js" olmak üzere iki dosya varsa, o zaman "b.js"nin "a.js"nin işlevselliğine erişmesi için "a.js", onu, özellikler ekleyerek dışa aktarmalıdır. dışa aktarma nesnesi:

 // a.js exports.verifyPassword = function(user, password, done) { ... }

Bu yapıldığında, "a.js" gerektiren herkese "verifyPassword" özellik işlevine sahip bir nesne verilecektir:

 // b.js require('a.js') // { verifyPassword: function(user, password, done) { ... } }

Ancak, bu işlevi bir nesnenin özelliği olarak değil de doğrudan dışa aktarmak istiyorsak ne olur? Bunu yapmak için dışa aktarımların üzerine yazabiliriz, ancak buna global bir değişken olarak bakmamalıyız:

 // a.js module.exports = function(user, password, done) { ... }

Modül nesnesinin bir özelliği olarak "dışa aktarmaları" nasıl ele aldığımıza dikkat edin. Buradaki "module.exports" ve "exports" arasındaki ayrım çok önemlidir ve genellikle yeni Node.js geliştiricileri arasında bir hayal kırıklığı nedenidir.

Hata #6: İçeriden Geri Aramalardan Hata Atma

JavaScript istisna kavramına sahiptir. Java ve C++ gibi istisna işleme desteğine sahip neredeyse tüm geleneksel dillerin sözdizimini taklit eden JavaScript, try-catch bloklarında istisnaları "atabilir" ve yakalayabilir:

 function slugifyUsername(username) { if(typeof username === 'string') { throw new TypeError('expected a string username, got '+(typeof username)) } // ... } try { var usernameSlug = slugifyUsername(username) } catch(e) { console.log('Oh no!') }

Ancak, try-catch, asenkron durumlarda beklediğiniz gibi davranmayacaktır. Örneğin, bir büyük try-catch bloğu ile çok sayıda eşzamansız etkinlik içeren büyük bir kod yığınını korumak istiyorsanız, bu mutlaka işe yaramaz:

 try { db.User.get(userId, function(err, user) { if(err) { throw err } // ... usernameSlug = slugifyUsername(user.username) // ... }) } catch(e) { console.log('Oh no!') }

"db.User.get" için geri arama eşzamansız olarak başlatılırsa, try-catch bloğunu içeren kapsam, geri aramanın içinden atılan bu hataları hala yakalayabilmesi için uzun zaman önce bağlam dışına çıkmış olurdu.

Node.js'de hatalar bu şekilde farklı bir şekilde işlenir ve bu, tüm geri arama işlevi argümanlarında (err, …) modelini izlemeyi zorunlu kılar - tüm geri aramaların ilk argümanının, eğer gerçekleşirse, bir hata olması beklenir. .

Hata #7: Sayının Tamsayı Veri Türü Olduğunu Varsaymak

JavaScript'teki sayılar kayan noktalardır - tamsayı veri türü yoktur. Float limitlerini vurgulayacak kadar büyük sayılarla sık sık karşılaşılmadığından, bunun bir sorun olmasını beklemezsiniz. İşte tam da o zaman bununla ilgili hatalar oluyor. Kayan nokta sayıları yalnızca belirli bir değere kadar tamsayı temsillerini tutabildiğinden, herhangi bir hesaplamada bu değeri aşmak hemen onu karıştırmaya başlayacaktır. Göründüğü kadar garip, aşağıdakiler Node.js'de doğru olarak değerlendirilir:

 Math.pow(2, 53)+1 === Math.pow(2, 53)

Ne yazık ki, JavaScript'teki sayılarla ilgili tuhaflıklar burada bitmiyor. Sayılar kayan noktalar olsa da tamsayı veri türleri üzerinde çalışan operatörler burada da çalışır:

 5 % 2 === 1 // true 5 >> 1 === 2 // true

Ancak, aritmetik işleçlerin aksine, bitsel işleçler ve kaydırma işleçleri, bu tür büyük "tamsayı" sayıların yalnızca sondaki 32 biti üzerinde çalışır. Örneğin, “Math.pow(2, 53)” öğesini 1 ile kaydırmaya çalışmak her zaman 0 olarak değerlendirilir. Aynı büyük sayı ile bit düzeyinde veya 1 yapmaya çalışmak 1 olarak değerlendirilir.

 Math.pow(2, 53) / 2 === Math.pow(2, 52) // true Math.pow(2, 53) >> 1 === 0 // true Math.pow(2, 53) | 1 === 1 // true

Nadiren büyük sayılarla uğraşmanız gerekebilir, ancak bunu yaparsanız, önemli matematiksel işlemleri büyük kesinlik sayıları üzerinde uygulayan çok sayıda büyük tamsayı kitaplığı vardır, örneğin node-bigint.

Hata #8: Akış API'lerinin Avantajlarını Görmezden Gelmek

Diyelim ki, içeriği başka bir web sunucusundan getirerek isteklere yanıt veren proxy benzeri küçük bir web sunucusu oluşturmak istiyoruz. Örnek olarak, Gravatar görüntülerine hizmet eden küçük bir web sunucusu oluşturacağız:

 var http = require('http') var crypto = require('crypto') http.createServer() .on('request', function(req, res) { var email = req.url.substr(req.url.lastIndexOf('/')+1) if(!email) { res.writeHead(404) return res.end() } var buf = new Buffer(1024*1024) http.get('http://www.gravatar.com/avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) { var size = 0 resp.on('data', function(chunk) { chunk.copy(buf, size) size += chunk.length }) .on('end', function() { res.write(buf.slice(0, size)) res.end() }) }) }) .listen(8080)

Node.js sorununun bu özel örneğinde, resmi Gravatar'dan alıyor, bir Tamponda okuyor ve ardından isteğe yanıt veriyoruz. Gravatar görüntülerinin çok büyük olmadığı göz önüne alındığında, bu o kadar da kötü bir şey değil. Ancak, proxy yaptığımız içeriğin boyutunun binlerce megabayt olduğunu hayal edin. Çok daha iyi bir yaklaşım şu olurdu:

 http.createServer() .on('request', function(req, res) { var email = req.url.substr(req.url.lastIndexOf('/')+1) if(!email) { res.writeHead(404) return res.end() } http.get('http://www.gravatar.com/avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) { resp.pipe(res) }) }) .listen(8080)

Burada görüntüyü alırız ve yanıtı müşteriye iletiriz. Hiçbir noktada, içeriği sunmadan önce tüm içeriği bir arabellekte okumamız gerekmez.

9. Hata: Console.log'u Hata Ayıklama Amaçlı Kullanmak

Node.js'de “console.log” konsola neredeyse her şeyi yazdırmanıza olanak tanır. Bir nesneyi ona iletin ve onu bir JavaScript nesnesi değişmezi olarak yazdıracaktır. Herhangi bir sayıdaki argümanı kabul eder ve hepsini düzgün bir şekilde boşlukla ayrılmış olarak yazdırır. Bir geliştiricinin, kodunda hata ayıklamak için bunu kullanmaya cazip gelmesinin birkaç nedeni vardır; ancak, gerçek kodda “console.log”dan kaçınmanız şiddetle tavsiye edilir. Hata ayıklamak için kodun her yerine “console.log” yazmaktan ve artık gerekmediğinde bunları yorumlamaktan kaçınmalısınız. Bunun yerine, hata ayıklama gibi yalnızca bunun için oluşturulmuş harika kitaplıklardan birini kullanın.

Bunun gibi paketler, uygulamayı başlattığınızda belirli hata ayıklama satırlarını etkinleştirmenin ve devre dışı bırakmanın uygun yollarını sağlar. Örneğin, hata ayıklama ile, DEBUG ortam değişkenini ayarlamayarak herhangi bir hata ayıklama satırının terminale yazdırılmasını önlemek mümkündür. Kullanımı basittir:

 // app.js var debug = require('debug')('app') debug('Hello, %s!', 'world')

Hata ayıklama satırlarını etkinleştirmek için, bu kodu DEBUG ortam değişkeni "app" veya "*" olarak ayarlayarak çalıştırmanız yeterlidir:

 DEBUG=app node app.js

Hata #10: Süpervizör Programlarını Kullanmamak

Node.js kodunuzun üretimde mi yoksa yerel geliştirme ortamınızda mı çalıştığından bağımsız olarak, programınızı düzenleyebilen bir süpervizör programı izleyicisi olması son derece yararlı bir şeydir. Modern uygulamaları tasarlayan ve uygulayan geliştiriciler tarafından sıklıkla önerilen bir uygulama, kodunuzun hızlı bir şekilde başarısız olmasını önerir. Beklenmeyen bir hata oluşursa, onu halletmeye çalışmayın, bunun yerine programınızın çökmesine izin verin ve bir süpervizörün birkaç saniye içinde yeniden başlatmasını sağlayın. Süpervizör programlarının faydaları, yalnızca çöken programları yeniden başlatmakla sınırlı değildir. Bu araçlar, programı kilitlendiğinde yeniden başlatmanıza ve bazı dosyalar değiştiğinde yeniden başlatmanıza izin verir. Bu, Node.js programlarını geliştirmeyi çok daha keyifli bir deneyim haline getirir.

Node.js için çok sayıda süpervizör programı mevcuttur. Örneğin:

  • pm2

  • sonsuza kadar

  • düğüm

  • süpervizör

Tüm bu araçlar artıları ve eksileri ile birlikte gelir. Bazıları aynı makinede birden fazla uygulamayı işlemek için iyidir, diğerleri ise günlük yönetiminde daha iyidir. Ancak, böyle bir programa başlamak istiyorsanız, bunların hepsi adil seçimlerdir.

Çözüm

Anlayabileceğiniz gibi, bu Node.js sorunlarından bazılarının programınız üzerinde yıkıcı etkileri olabilir. Node.js'de en basit şeyleri uygulamaya çalışırken bazıları hayal kırıklığına uğramanıza neden olabilir. Node.js, yeni başlayanların başlamasını son derece kolaylaştırmış olsa da, yine de karıştırmanın kolay olduğu alanlar var. Diğer programlama dillerinden geliştiriciler bu sorunlardan bazılarıyla ilgili olabilir, ancak bu hatalar yeni Node.js geliştiricileri arasında oldukça yaygındır. Neyse ki, bunlardan kaçınmak kolaydır. Umarım bu kısa kılavuz, yeni başlayanların Node.js'de daha iyi kod yazmalarına ve hepimiz için kararlı ve verimli yazılımlar geliştirmelerine yardımcı olur.