TypeScript, JavaScript'in üzerine kurulu, güçlü bir şekilde yazılmış bir programlama dili olduğunu ve her ölçekte daha iyi araçlar sağladığını iddia ediyor. Bununla birlikte, TypeScript any
türü içerir ve bu genellikle kod tabanına gizlice sızarak TypeScript'in birçok avantajının kaybolmasına neden olabilir.
Bu makalede TypeScript projelerinde any
türün kontrolünü ele almanın yolları araştırılmaktadır. TypeScript'in gücünü açığa çıkarmaya, üstün tür güvenliği elde etmeye ve kod kalitesini artırmaya hazır olun.
TypeScript, geliştirici deneyimini ve üretkenliğini artırmak için bir dizi ek araç sağlar:
Ancak kod tabanınızda any
türü kullanmaya başladığınız anda yukarıda sıralanan tüm avantajları kaybedersiniz. any
tip, tip sisteminde tehlikeli bir boşluktur ve bunun kullanılması, tip kontrolüne bağlı tüm araçların yanı sıra tüm tip kontrolü yeteneklerini de devre dışı bırakır. Sonuç olarak TypeScript'in tüm avantajları kaybolur: hatalar gözden kaçırılır, kod düzenleyiciler daha az kullanışlı hale gelir ve daha fazlası.
Örneğin aşağıdaki örneği göz önünde bulundurun:
function parse(data: any) { return data.split(''); } // Case 1 const res1 = parse(42); // ^ TypeError: data.split is not a function // Case 2 const res2 = parse('hello'); // ^ any
Yukarıdaki kodda:
parse
işlevi içindeki otomatik tamamlamayı kaçıracaksınız. data.
düzenleyicinizde, data
için mevcut yöntemlere ilişkin doğru öneriler size sunulmayacaktır.TypeError: data.split is not a function
çünkü bir dize yerine bir sayı ilettik. TypeScript, any
tür denetimini devre dışı bıraktığından hatayı vurgulayamıyor.res2
değişkeni de any
türe sahiptir. Bu, any
birinin tek bir kullanımının kod tabanının büyük bir kısmı üzerinde basamaklı bir etkiye sahip olabileceği anlamına gelir.
any
kullanılması yalnızca aşırı durumlarda veya prototip oluşturma ihtiyaçları için uygundur. Genel olarak TypeScript'ten en iyi şekilde yararlanmak için any
kullanmaktan kaçınmak daha iyidir.
Bir kod tabanındaki any
türün kaynaklarının farkında olmak önemlidir çünkü açıkça any
yazmak tek seçenek değildir. any
bir türü kullanmaktan kaçınmak için elimizden geleni yapmamıza rağmen, bazen dolaylı olarak kod tabanına gizlice girebilir.
Bir kod tabanında any
türden dört ana kaynak vardır:
any
kod tabanında açık kullanımı.
İlk iki nokta için tsconfig'te Önemli Hususlar ve Standart Kütüphane Türlerinin Geliştirilmesi konularında zaten makaleler yazdım. Projelerinizde tip güvenliğini artırmak istiyorsanız lütfen bunlara göz atın.
Bu sefer kod tabanındaki any
türün görünümünü kontrol etmeye yönelik otomatik araçlara odaklanacağız.
ESLint , web geliştiricileri tarafından en iyi uygulamaları ve kod biçimlendirmesini sağlamak için kullanılan popüler bir statik analiz aracıdır. Kodlama stillerini uygulamak ve belirli kurallara uymayan kodları bulmak için kullanılabilir.
ESLint, typectipt-eslint eklentisi sayesinde TypeScript projeleriyle de kullanılabilir. Büyük olasılıkla, bu eklenti projenize zaten yüklenmiştir. Ancak değilse resmi başlangıç kılavuzunu takip edebilirsiniz.
typescript-eslint
için en yaygın yapılandırma aşağıdaki gibidir:
module.exports = { extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', ], plugins: ['@typescript-eslint'], parser: '@typescript-eslint/parser', root: true, };
Bu yapılandırma, eslint
TypeScript'i sözdizimi düzeyinde anlamasına olanak tanıyarak, bir kodda manuel olarak yazılan türler için geçerli olan basit eslint kuralları yazmanıza olanak tanır. Örneğin, any
şeyin açıkça kullanımını yasaklayabilirsiniz.
recommended
ön ayar, kod doğruluğunu iyileştirmeyi amaçlayan, dikkatle seçilmiş bir dizi ESLint kuralını içerir. Ön ayarın tamamının kullanılması tavsiye edilse de, bu makalenin amacı doğrultusunda yalnızca no-explicit-any
kuralına odaklanacağız.
TypeScript'in katı modu, ima edilen any
ifadesinin kullanımını engeller, ancak any
açıkça kullanılmasını engellemez. no-explicit-any
kural, kod tabanında any
yere manuel olarak yazılmasını yasaklamaya yardımcı olur.
// ❌ Incorrect function loadPokemons(): any {} // ✅ Correct function loadPokemons(): unknown {} // ❌ Incorrect function parsePokemons(data: Response<any>): Array<Pokemon> {} // ✅ Correct function parsePokemons(data: Response<unknown>): Array<Pokemon> {} // ❌ Incorrect function reverse<T extends Array<any>>(array: T): T {} // ✅ Correct function reverse<T extends Array<unknown>>(array: T): T {}
Bu kuralın temel amacı takım genelinde any
kullanımın engellenmesidir. Bu, ekibin projede any
kullanımının caydırılmadığı konusundaki anlaşmasını güçlendirmenin bir yoludur.
Bu çok önemli bir hedeftir çünkü any
birinin tek bir kullanımı bile tür çıkarımından dolayı kod tabanının önemli bir kısmı üzerinde basamaklı bir etkiye sahip olabilir. Ancak bu hala nihai tip güvenliğe ulaşmaktan çok uzak.
Her ne kadar açıkça kullanılan any
ile ilgilenmiş olsak da, bir projenin bağımlılıkları içinde npm paketleri ve TypeScript'in standart kütüphanesi de dahil olmak üzere hala birçok ima edilen any
vardır.
Herhangi bir projede görülmesi muhtemel olan aşağıdaki kodu göz önünde bulundurun:
const response = await fetch('https://pokeapi.co/api/v2/pokemon'); const pokemons = await response.json(); // ^? any const settings = JSON.parse(localStorage.getItem('user-settings')); // ^? any
Hem pokemons
hem de settings
değişkenleri örtülü olarak any
türe verildi. Bu durumda ne no-explicit-any
ne de TypeScript'in katı modu bizi uyaracaktır. Henüz değil.
Bunun nedeni, response.json()
ve JSON.parse()
türlerinin TypeScript'in standart kitaplığından gelmesi ve bu yöntemlerin açık any
açıklama içermesidir. Değişkenlerimiz için hâlâ manuel olarak daha iyi bir tür belirleyebiliriz ancak standart kitaplıkta bunlardan any
birinin yaklaşık 1.200 örneği vardır. any
standart kitaplıktan kod tabanımıza gizlice girebileceği tüm durumları hatırlamak neredeyse imkansızdır.
Aynı şey dış bağımlılıklar için de geçerli. Npm'de pek çok kötü yazılmış kitaplık var ve bunların çoğu hala JavaScript ile yazılıyor. Sonuç olarak, bu tür kitaplıkların kullanılması, kod tabanında pek çok örtülü any
bulunmasına kolaylıkla yol açabilir.
Genel olarak any
kodumuza sızmasının hâlâ birçok yolu vardır.
İdeal olarak, TypeScript'te derleyicinin herhangi bir nedenle any
türü alan herhangi bir değişken hakkında şikayette bulunmasını sağlayan bir ayara sahip olmak isteriz. Maalesef böyle bir ayar şu anda mevcut değil ve eklenmesi de beklenmiyor.
Bu davranışı typescript-eslint
eklentisinin type-checked modunu kullanarak başarabiliriz. Bu mod, TypeScript derleyicisinden ESLint kurallarına kadar eksiksiz tür bilgisi sağlamak için TypeScript ile birlikte çalışır. Bu bilgilerle, TypeScript'in tür denetleme yeteneklerini esasen genişleten daha karmaşık ESLint kuralları yazmak mümkündür. Örneğin, bir kural, any
nasıl elde edildiğine bakılmaksızın, any
türdeki tüm değişkenleri bulabilir.
Yazıma duyarlı kuralları kullanmak için ESLint yapılandırmasını biraz değiştirmeniz gerekir:
module.exports = { extends: [ 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-type-checked', ], plugins: ['@typescript-eslint'], parser: '@typescript-eslint/parser', + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, root: true, };
typescript-eslint
için tür çıkarımını etkinleştirmek için parserOptions
ESLint yapılandırmasına ekleyin. Ardından, recommended
ön ayarı recommended-type-checked
ile değiştirin. İkinci ön ayar yaklaşık 17 yeni güçlü kural ekler. Bu makalenin amacı doğrultusunda bunlardan yalnızca 5 tanesine odaklanacağız.
no-unsafe-argument
kuralı, any
türünde bir değişkenin parametre olarak iletildiği işlev çağrılarını arar. Bu olduğunda, yazım denetimi kaybolur ve güçlü yazmanın tüm faydaları da kaybolur.
Örneğin, parametre olarak bir nesne gerektiren bir saveForm
fonksiyonunu ele alalım. JSON'u aldığımızı, ayrıştırdığımızı ve any
bir tür elde ettiğimizi varsayalım.
// ❌ Incorrect function saveForm(values: FormValues) { console.log(values); } const formValues = JSON.parse(userInput); // ^? any saveForm(formValues); // ^ Unsafe argument of type `any` assigned // to a parameter of type `FormValues`.
Bu parametreyle saveForm
fonksiyonunu çağırdığımızda, no-unsafe-argument
kuralı bunu güvensiz olarak işaretler ve value
değişkeni için uygun türü belirtmemizi gerektirir.
Bu kural, işlev bağımsız değişkenleri içindeki iç içe geçmiş veri yapılarını derinlemesine inceleyecek kadar güçlüdür. Bu nedenle, nesnelerin işlev bağımsız değişkenleri olarak iletilmesinin hiçbir zaman türlenmemiş veriler içermeyeceğinden emin olabilirsiniz.
// ❌ Incorrect saveForm({ name: 'John', address: JSON.parse(addressJson), // ^ Unsafe assignment of an `any` value. });
Hatayı düzeltmenin en iyi yolu TypeScript'in tür daraltmasını veya Zod veya Superstruct gibi bir doğrulama kitaplığını kullanmaktır. Örneğin, ayrıştırılan verilerin kesin türünü daraltan parseFormValues
fonksiyonunu yazalım.
// ✅ Correct function parseFormValues(data: unknown): FormValues { if ( typeof data === 'object' && data !== null && 'name' in data && typeof data['name'] === 'string' && 'address' in data && typeof data.address === 'string' ) { const { name, address } = data; return { name, address }; } throw new Error('Failed to parse form values'); } const formValues = parseFormValues(JSON.parse(userInput)); // ^? FormValues saveForm(formValues);
any
türün, unknown
kabul eden bir işleve bağımsız değişken olarak iletilmesine izin verildiğini unutmayın; çünkü bunu yapmakla ilgili herhangi bir güvenlik sorunu yoktur.
Veri doğrulama işlevlerini yazmak, özellikle büyük miktarda veriyle uğraşırken sıkıcı bir iş olabilir. Bu nedenle, bir veri doğrulama kütüphanesinin kullanılması dikkate alınmalıdır. Örneğin Zod'da kod şöyle görünecektir:
// ✅ Correct import { z } from 'zod'; const schema = z.object({ name: z.string(), address: z.string(), }); const formValues = schema.parse(JSON.parse(userInput)); // ^? { name: string, address: string } saveForm(formValues);
no-unsafe-assignment
kuralı, bir değerin any
türe sahip olduğu değişken atamaları arar. Bu tür atamalar, derleyiciyi bir değişkenin belirli bir türe sahip olduğunu düşünmesine neden olabilirken, veriler aslında farklı bir türe sahip olabilir.
Önceki JSON ayrıştırma örneğini düşünün:
// ❌ Incorrect const formValues = JSON.parse(userInput); // ^ Unsafe assignment of an `any` value
no-unsafe-assignment
kuralı sayesinde, formValues
başka bir yere aktarmadan önce bile any
türü yakalayabiliriz. Sabitleme stratejisi aynı kalıyor: Değişkenin değerine belirli bir tür sağlamak için tür daraltmayı kullanabiliriz.
// ✅ Correct const formValues = parseFormValues(JSON.parse(userInput)); // ^? FormValues
Bu iki kural çok daha az sıklıkla tetiklenir. Ancak deneyimlerime dayanarak, kötü yazılmış üçüncü taraf bağımlılıklarını kullanmaya çalıştığınızda bunlar gerçekten faydalıdır.
no-unsafe-member-access
kuralı, bir değişkenin any
türe sahip olması durumunda nesne özelliklerine erişmemizi engeller; çünkü bu, null
veya undefined
olabilir.
no-unsafe-call
kuralı, any
türden bir değişkeni işlev olarak çağırmamızı engeller, çünkü bu bir işlev olmayabilir.
untyped-auth
adında kötü yazılmış bir üçüncü taraf kitaplığımızın olduğunu düşünelim:
// ❌ Incorrect import { authenticate } from 'untyped-auth'; // ^? any const userInfo = authenticate(); // ^? any ^ Unsafe call of an `any` typed value. console.log(userInfo.name); // ^ Unsafe member access .name on an `any` value.
Linter iki konuyu vurgulamaktadır:
authenticate
işlevinin çağrılması güvenli olmayabilir, çünkü önemli argümanları işleve iletmeyi unutabiliriz.null
olacağından name
özelliğinin userInfo
nesnesinden okunması güvenli değildir.
Bu hataları düzeltmenin en iyi yolu, türü kesin olarak belirlenmiş bir API içeren bir kitaplık kullanmayı düşünmektir. Ancak bu bir seçenek değilsekitaplık türlerini kendiniz artırabilirsiniz . Sabit kitaplık türlerine sahip bir örnek şuna benzer:
// ✅ Correct import { authenticate } from 'untyped-auth'; // ^? (login: string, password: string) => Promise<UserInfo | null> const userInfo = await authenticate('test', 'pwd'); // ^? UserInfo | null if (userInfo) { console.log(userInfo.name); }
no-unsafe-return
kuralı, daha spesifik bir şey döndürmesi gereken bir işlevden any
türün yanlışlıkla döndürülmemesine yardımcı olur. Bu gibi durumlar, derleyiciyi, döndürülen değerin belirli bir türe sahip olduğunu düşünmesine neden olabilirken, veriler gerçekte farklı bir türe sahip olabilir.
Örneğin, JSON'u ayrıştıran ve iki özelliğe sahip bir nesne döndüren bir fonksiyonumuz olduğunu varsayalım.
// ❌ Incorrect interface FormValues { name: string; address: string; } function parseForm(json: string): FormValues { return JSON.parse(json); // ^ Unsafe return of an `any` typed value. } const form = parseForm('null'); console.log(form.name); // ^ TypeError: Cannot read properties of null
parseForm
fonksiyonu, ayrıştırılan değer kontrol edilmediğinden, kullanıldığı programın herhangi bir bölümünde çalışma zamanı hatalarına neden olabilir. no-unsafe-return
kuralı bu tür çalışma zamanı sorunlarını önler.
Ayrıştırılan JSON'un beklenen türle eşleştiğinden emin olmak için doğrulama ekleyerek bunu düzeltmek kolaydır. Bu sefer Zod kütüphanesini kullanalım:
// ✅ Correct import { z } from 'zod'; const schema = z.object({ name: z.string(), address: z.string(), }); function parseForm(json: string): FormValues { return schema.parse(JSON.parse(json)); }
Tür denetimli kuralların kullanılması, ESLint'in tüm türleri çıkarması için TypeScript'in derleyicisini çağırması gerektiğinden performans kaybıyla birlikte gelir. Bu yavaşlama esas olarak linter'ı ön işleme kancalarında ve CI'da çalıştırırken fark edilir, ancak bir IDE'de çalışırken fark edilmez. Tür denetimi, IDE başlangıcında bir kez gerçekleştirilir ve ardından siz kodu değiştirdikçe türleri günceller.
Yalnızca türlerin çıkarımının, tsc
derleyicisinin olağan çağrılmasından daha hızlı çalıştığını belirtmekte fayda var. Örneğin, yaklaşık 1,5 milyon satırlık TypeScript kodu içeren en son projemizde, tsc
aracılığıyla tür kontrolü yaklaşık 11 dakika sürerken, ESLint'in türe duyarlı kurallarının önyükleme yapması için gereken ek süre yalnızca yaklaşık 2 dakikadır.
Ekibimiz için, türe duyarlı statik analiz kurallarının kullanılmasıyla sağlanan ek güvenlik, ödün vermeye değerdir. Daha küçük projelerde bu kararı vermek daha da kolaydır.
TypeScript projelerinde any
birinin kullanımını kontrol etmek, optimum tür güvenliği ve kod kalitesi elde etmek için çok önemlidir. Geliştiriciler typescript-eslint
eklentisini kullanarak kod tabanlarındaki any
türün oluşumlarını tanımlayabilir ve ortadan kaldırabilir, böylece daha sağlam ve bakımı kolay bir kod tabanı elde edilebilir.
Türe duyarlı eslint kuralları kullanıldığında, any
bir anahtar kelimenin kod tabanımızda görünmesi bir hata veya gözden kaçırma yerine kasıtlı bir karar olacaktır. Bu yaklaşım bizi kendi any
, standart kütüphanede ve üçüncü taraf bağımlılıklarında kullanmaktan korur.
Genel olarak, türe duyarlı bir linter, Java, Go, Rust ve diğerleri gibi statik olarak yazılan programlama dillerine benzer bir tür güvenliği düzeyi elde etmemizi sağlar. Bu, büyük projelerin geliştirilmesini ve bakımını büyük ölçüde basitleştirir.
Umarım bu makaleden yeni bir şeyler öğrenmişsinizdir. Okuduğunuz için teşekkürler!