الهدف هو إظهار أين يعطي TS وهم الأمن ، مما يسمح لك بالحصول على أخطاء أثناء تشغيل البرنامج.لن نتحدث عن الخلل ، في TS يوجد ما يكفي من1500 بق مفتوح و 6000 مغلق ('هو: المشكلة هي: تسمية مفتوحة: خطأ').سيتم النظر في جميع الأمثلة مع:- وضع TS الصارم قيد التشغيل (كتب مقالًا أثناء الفهم)
- بدون صريح "any": "as any"، "Objects"، "Function"، {[key: string]: unknown}
- بدون "أي" ضمنيًا: (noImplicitAny): عمليات الاستيراد غير المنسوخة (ملفات JS الخالصة) ، استدلال النوع غير صحيح
- بدون تخمينات خاطئة حول الأنواع: استجابة من الخادم ، طباعة مكتبات جهات خارجية
المحتوى:- المقدمة
- الأنواع الاسمية ، الأنواع المخصصة - عندما تبدو الأشياء كما هي ، ولكنها مختلفة جدًا
- تباين النوع ، الأنواع الدقيقة - حول العلاقة بين الأنواع
- إبطال التحسين - الحديث عن الثقة
- الاستثناءات - هل يستحق الاعتراف عند العبث؟
- العمليات غير الآمنة - الثقة ليست دائمًا جيدة
- حالات المكافأة - التحقق من النوع في مرحلة مراجعة العلاقات العامة
- استنتاج
المقدمة
هل من الصعب كتابة دالة لإضافة رقمين في شبيبة؟ اتخاذ تنفيذ ساذجfunction sum(a, b) {
return a + b;
}
دعونا نتحقق من تطبيقنا لـ `sum (2، 2) === 4` ، هل يبدو أن كل شيء يعمل؟ ليس في الحقيقة ، عندما نصف وظيفة ، يجب أن نفكر في جميع أنواع قيم المدخلات ، وكذلك ما يمكن أن تعيده الدالة1.1 + 2.7
NaN + 2
99999999999999992 + 99999999999999992
2n + 2
{} + true
2 + '2'
Soundness هي قدرة المحلل على إثبات عدم وجود أخطاء أثناء تشغيل البرنامج. إذا تم قبول البرنامج من قبل المحلل ، فمن المؤكد أنه آمن.البرنامج الآمن هو برنامج يمكنه العمل إلى الأبد دون أخطاء. أولئك. البرنامج لن يتلف أو يرمي الأخطاء.برنامج صحيح - برنامج يقوم بما ينبغي ولا يفعل ما لا ينبغي. يعتمد التصحيح على تنفيذ منطق الأعمال.يمكن للأنواع أن تثبت أن البرنامج ككل آمن ، ويختبر أن البرنامج آمن وصحيح فقط ضمن بيانات الاختبار (تغطية 100٪ ، وغياب "المسوخ" من stryker ، واجتياز اختبار قائم على الممتلكات وما إلى ذلك لا يمكن إثبات أي شيء ، والتراخيص يقلل من المخاطر). هناك أساطير تثبت النظرية أنها يمكن أن تثبت صحة البرنامج.من المهم فهم فلسفة TS ، لفهم ما تحاول الأداة حله ، وما هو مهم ، وما لا تحاول حله. تتخطىملاحظة حول SoundnessTS بعض العمليات غير المؤكدة في مرحلة التجميع. تم التفكير بعناية في الأماكن ذات السلوك غير السليم.أهداف التصميمليست الهدف من TS - لإنشاء نظام نوع مع ضمان للأمان ، وبدلاً من ذلك التركيز على التوازن بين السلامة والإنتاجيةمثال الهيكل:المشكلة ليست السلوك الآمن ، القائمة قد لا تكون كاملة ، هذا ما وجدته في المقالات والتقارير ، قضايا بوابة TS.الاقتراح هو قضية TS مفتوحة قبل 3-4 سنوات ، مع مجموعة من التعليقات والتفسيرات المثيرة للاهتمام من قبل المؤلفيننصيحة - IMHO للمؤلف ، ما يعتبره المؤلف الممارسات الجيدةالكتابة الهيكلية مقابل الكتابة الاسمية
الكتابة الهيكلية مقابل الكتابة الاسمية 1. المشكلة
الكتابة الهيكلية - عندما لا تراعي مقارنة الأنواع أسمائها أو مكان الإعلان عنها ، ويتم مقارنة الأنواع وفقًا لـ "البنية".نريد إرسال الحرف `sendEmail` إلى العنوان الصحيح` ValidatedEmail` ، وهناك وظيفة للتحقق من العنوان` validateEmail` الذي يُرجع العنوان الصحيح` ValidatedEmail`. للأسف TS يسمح لك بإرسال أي سلسلة إلى `sendEmail` ، لأن `ValidatedEmail` لـ TS لا يختلف عن` string`type ValidatedEmail = string;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail("asdf@gmail.com"));
// Should be error!
sendEmail("asdf@gmail.com");
الكتابة الهيكلية مقابل الكتابة الاسمية 1. العرض
github.com/microsoft/TypeScript/issues/202أدخل الكلمة الأساسية "الاسمية" حتى يتم تحديد الأنواع بشكل رمزي. الآن يمكننا منع تمرير `سلسلة` فقط حيث يتوقع` ValidatedEmail`nominal type ValidatedEmail = string;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail('asdf@gmail.com'));
// Error!
sendEmail('asdf@gmail.com');
الكتابة الهيكلية مقابل الاسمية 1. تلميح
يمكننا إنشاء نوع `Opaque` ، والذي سيأخذ بعضًا من` T` وإعطائه تفردًا من خلال دمجه مع نوع تم إنشاؤه من `K` الذي تم تمريره. يمكن أن يكون `K` إما رمزًا فريدًا (` رمزًا فريدًا`) أو سلسلة (عندئذٍ سيكون من الضروري التأكد من أن هذه السلاسل فريدة).type Opaque<K extends symbol | string, T>
= T & { [X in K]: never };
declare const validatedEmailK: unique symbol;
type ValidatedEmail = Opaque<typeof validatedEmailK, string>;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail('asdf@gmail.com'));
// Argument of type '"asdf@gmail.com"' is not assignable
// to parameter of type 'Opaque<unique symbol, string>'.
sendEmail('asdf@gmail.com');
الكتابة الهيكلية مقابل الكتابة الاسمية 2. المشكلة
لدينا فئة الدولار واليورو ، كل فئة لديها طريقة إضافة لإضافة الدولار إلى الدولار واليورو إلى اليورو. بالنسبة لـ TS ، هذه الفئات متساوية هيكليًا ويمكننا إضافة الدولار إلى اليورو.export class Dollar {
value: number;
constructor(value: number) {
this.value = value;
}
add(dollar: Dollar): Dollar {
return new Dollar(dollar.value + this.value);
}
}
class Euro {
value: number;
constructor(value: number) {
this.value = value;
}
add(euro: Euro): Euro {
return new Euro(euro.value + this.value);
}
}
const dollars100 = new Dollar(100);
const euro100 = new Euro(100);
dollars100.add(dollars100);
euro100.add(euro100);
dollars100.add(euro100);
الكتابة الهيكلية مقابل الكتابة الاسمية 2. العرض
github.com/microsoft/TypeScript/issues/202الجملة هي نفسها ، مع "الاسمي" ، ولكن منذ نظرًا لأن الفصول يمكن أن تصبح اسمًا سحريًا (المزيد عن ذلك لاحقًا) ، يتم النظر في إمكانيات إجراء مثل هذا التحول بطريقة أكثر وضوحًا.الكتابة الهيكلية مقابل الاسمية 1. تلميح
إذا كان للفصل حقل خاص (أصلي بـ `#` أو من TS c `خاص`) ، فعندئذ يصبح الفصل بطريقة سحرية ، يمكن أن يكون الاسم والقيمة أي شيء. يتم استخدام `!` (تأكيد التعيين المحدد) لمنع TS من الشتائم في حقل غير مهيأ (يتم تمكين العلامات الصارمة ، علامات صارمة على خصائص التعريف).class Dollar {
private desc!: never;
value: number;
constructor(value: number) {
this.value = value;
}
add(dollar: Dollar) {
return new Dollar(dollar.value + this.value);
}
}
class Euro {
private desc!: never;
value: number;
constructor(value: number) {
this.value = value;
}
add(euro: Euro) {
return new Euro(euro.value + this.value);
}
}
const dollars100 = new Dollar(100);
const euro100 = new Euro(100);
dollars100.add(dollars100);
euro100.add(euro100);
dollars100.add(euro100);
تباين النوع 1. المشكلة
خيار البرمجة ، باختصار ، هو القدرة على تمرير Supertype / Subtype هناك ، حيث يتوقع النوع. على سبيل المثال ، هناك شكل هرمي -> دائرة -> مستطيل ، هل من الممكن نقل أو إرجاع شكل / مستطيل إذا كان من المتوقع وجود دائرة؟البديل في برمجة الهابر ، SO .يمكننا تمرير النوع مع الحقل الذي يقع فيه الرقم إلى دالة تتوقع الحقل كسلسلة أو رقم ، وتحوير الكائن المرسل في الجسم ، وتغيير الحقل إلى سلسلة. أولئك. `{status: number} مثل {status: number | string} مثل {status: string} `هنا خدعة مثل تحويل رقم إلى سلسلة ، مما يتسبب في حدوث خطأ مفاجئ .function changeStatus(arg: { status: number | string }) {
arg.status = "NotFound";
}
const error: { status: number } = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
نوع التباين 1. عرض
github.com/Microsoft/TypeScript/issues/10717يُقترح إدخال "داخل / خارج" للحد صراحةً من التغاير / التعارض في الأدوية الجنسية.function changeStatus<
out T extends {
status: number | string;
}
>(arg: T) {
arg.status = "NotFound";
}
const error: { status: number } = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
نوع التباين 1. نصيحة
إذا عملنا بهياكل غير قابلة للتغيير ، فلن يكون هناك مثل هذا الخطأ (لقد قمنا بالفعل بتمكين إشارة نوع الوظيفة الصارمة).function changeStatus(arg: Readonly<{ status: number | string }>) {
arg.status = "NotFound";
}
const error: Readonly<{ status: number }> = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
نوع التباين 1. مكافأة
Readonly قابل للتخصيص علىgithub.com/Microsoft/TypeScript/issues/13347github.com/microsoft/TypeScript/pull/6532#issuecomment-174356151ولكن حتى إذا أنشأنا نوع Readonly ، فلن يمنع TS من الانتقال إلى الوظيفة حيث غير متوقع للقراءة فقط `Readonly <{readonly status: number}> مثل {status: number | string} مثل {status: string} `function changeStatus(arg: { status: number | string }) {
arg.status = "NotFound";
}
const error: Readonly<{ readonly status: number }>
= { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
نوع التباين 2. المشكلة
قد تحتوي الكائنات على حقول إضافية لا تحتوي عليها الأنواع المقابلة لها: `{message: string؛ status: string} مثل {message: string} `. بسبب بعض العمليات قد لا تكون آمنةconst error: { message: string; status: string } = {
message: "No data",
status: "NotFound"
};
function updateError(arg: { message: string }) {
const defaultError = { message: "Not found", status: 404 };
const newError: { message: string; status: number }
= { ...defaultError, ...arg };
console.log(newError.status.toFixed());
}
updateError(error);
يعتقد TS أنه نتيجة لعملية الدمج ، ستكون حالة {... {message: string، status: number}، ... {message: string}} `رقمًا.في الواقع ، `{... {message:" Not found "، status: 404}، ... {message:" No data "، status:" NotFound "}،}` status - string.نوع التباين 2. العرض
github.com/microsoft/TypeScript/issues/12936تقديمه ل`نوع Exact` أو بناء جملة مماثل القول بأن نوع لا يمكن أن تحتوي حقول إضافية.const error: Exact<{ message: string; }> = {
message: "No data",
};
function updateError(arg: Exact<{ message: string }>) {
const defaultError = { message: "Not found", status: 404, };
const newError = { ...defaultError, ...arg };
console.log(newError.status.toFixed());
}
updateError(error);
نوع التباين 2. تلميح
دمج الكائنات من خلال سرد الحقول بشكل صريح أو تصفية الحقول غير المعروفة.const error: { message: string; status: string } = {
message: "No data",
status: "NotFound"
};
function updateError(arg: { message: string }) {
const defaultError = { message: "Not found", status: 404 };
const newError = { ...defaultError, message: arg.message };
console.log(newError.status.toFixed());
}
updateError(error);
إبطال التنقية. مشكلة
بعد أن أثبتنا شيئًا بشأن الحالة الخارجية ، فإن وظائف الاتصال ليست آمنة ، لأن لا توجد ضمانات بأن الوظائف لا تغير هذه الحالة الخارجية:export function logAge(name: string, age: number) {
console.log(`${name} will lose ${age.toFixed()}`);
person.age = "PLACEHOLDER";
}
const person: { name: string; age: number | string } = {
name: "Person",
age: 42
};
if (typeof person.age === "number") {
logAge(person.name, person.age);
logAge(person.name, person.age);
}
إبطال التنقية. جملة او حكم على
github.com/microsoft/TypeScript/issues/7770#issuecomment-334919251إضافة `pure` معدل للوظائف، هذه الإرادة على الأقل تسمح لك أن تثق هذه الوظائفإبطال التنقية. تلميح
استخدم بنيات البيانات غير القابلة للتغيير ، عندها ستكون استدعاء الوظيفة آمنة مسبقًا لعمليات الفحص السابقة.علاوة
نوع التدفق قوي لدرجة أنه لا يحتوي على جميع المشاكل المذكورة أعلاه ، ولكنه يعمل بحيث لا أوصي باستخدامه.الاستثناءات. مشكلة
لا يساعد TS في العمل مع الاستثناءات بأي شكل من الأشكال ؛ لا يوجد شيء واضح على توقيع الوظيفة.import { JokeError } from "../helpers";
function getJoke(isFunny: boolean): string {
if (isFunny) {
throw new JokeError("No funny joke");
}
return "Duh";
}
const joke: string = getJoke(true);
console.log(joke);
الاستثناءات. جملة او حكم على
github.com/microsoft/TypeScript/issues/13219يقترح إدخال بناء جملة يسمح بوصف الاستثناءات بوضوح في توقيع الوظيفةimport { JokeError } from '../helpers';
function getJoke(isFunny: boolean): string | throws JokeError {
}
function getJokeSafe(isFunny: boolean): string {
try {
return getJoke(isFunny);
} catch (error) {
if (error instanceof JokeError) {
return "";
} else {
return error as never;
}
}
}
console.log(getJokeSafe(true));
الاستثناءات. علاوة
github.com/microsoft/TypeScript/issues/6283لسبب ما ، في TS ، يتجاهل تعريف نوع Promise نوع الخطأconst promise1: Promise<number> = Promise.resolve(42);
const promise: Promise<never> = Promise.reject(new TypeError());
interface PromiseConstructor {
new <T>(
executor: (
resolve: (value?: T | PromiseLike<T>) => void,
reject: (reason?: any) => void
) => void
): Promise<T>;
}
الاستثناءات. تلميح
خذ أيًا من الحاوية مثل Promise ، مع أفضل كتابة فقط. ( إما مثال على التنفيذ )import { Either, exhaustiveCheck, JokeError } from "../helpers";
function getJoke(isFunny: boolean): Either<JokeError, string> {
if (isFunny) {
return Either.left(new JokeError("No funny joke"));
}
return Either.right("Duh");
}
getJoke(true)
.mapLeft(error => {
if (error instanceof JokeError) {
console.log("JokeError");
} else {
exhaustiveCheck(error);
}
})
.mapRight(joke => console.log(joke));
عمليات غير آمنة. مشكلة
إذا كان لدينا مجموعة ذات حجم ثابت ، فيمكن أن تضمن TS وجود شيء في الفهرس المطلوب. لن يعمل هذا مع المصفوفة وسوف يثق بنا TS
export const constNumbers: readonly [1, 2, 3]
= [1, 2, 3] as const;
console.log(constNumbers[100].toFixed());
const dynamicNumbers: number[] = [1, 2, 3];
console.log(dynamicNumbers[100].toFixed());
عمليات غير آمنة. جملة او حكم على
github.com/microsoft/TypeScript/issues/13778يُقترح إضافة `undefined` إلى نوع الإرجاع` T` للوصول إلى الفهرس إلى الصفيف. ولكن في هذه الحالة ، عند الوصول إلى أي فهرس ، سيكون عليك استخدام "؟" أو إجراء فحوص صريحة.
const dynamicNumbers: number[] = [1, 2, 3];
console.log(dynamicNumbers[100].toFixed());
console.log(dynamicNumbers[100]?.toFixed());
if (typeof dynamicNumbers[100] === 'number') {
console.log(dynamicNumbers[100].toFixed());
}
عمليات غير آمنة. تلميح
من أجل عدم إنتاج كيانات تتجاوز الحاجة ، نأخذ الحاوية المعروفة سابقًا `إما 'ونكتب وظيفة آمنة للعمل مع الفهرس ، والتي ستعود` إما <فارغة ، T> `.import { Either } from "../helpers";
function safeIndex<T>(
array: T[],
index: number,
): Either<null, T> {
if (index in array) {
return Either.right(array[index]);
}
return Either.left(null);
}
const dynamicNumbers: number[] = [1, 2, 3];
safeIndex(dynamicNumbers, 100)
.mapLeft(() => console.log("Nothing"))
.mapRight(el => el + 2)
.mapRight(el => console.log(el.toFixed()));
علاوة وظائف إعادة التحميل
إذا أردنا أن نقول أن الدالة تأخذ بضعة أسطر وتعيد سلسلة أو تأخذ عددًا من الأرقام وترجع رقمًا ، فعند التنفيذ ستكون هذه التوقيعات متجاورة ، ويجب على المبرمج ضمان صحتها ، ولكن على TS.ملاحظة نظرة على البديل من خلال الأنواع العامة والشرطية:function add(a: string, b: string): string;
function add(a: number, b: number): number;
function add(a: string | number,
b: string | number,
): string | number {
return `${a} + ${b}`;
}
const sum: number = add(2, 2);
sum.toFixed();
علاوة اكتب حارس
تثق TS في المبرمج بأن `isSuperUser` يحدد بشكل صحيح من هو" SuperUser "وإذا تمت إضافة" Vasya "، فلن تكون هناك مطالبات.PS يستحق التفكير في كيفية تمييز الأنواع الموجودة بالفعل في مرحلة اتحادهم الموسومtype SimpleUser = { name: string };
type SuperUser = {
name: string;
isAdmin: true;
permissions: string[]
};
type Vasya = { name: string; isAdmin: true; isGod: true };
type User = SimpleUser | SuperUser | Vasya;
function isSuperUser(user: User): user is SuperUser {
return "isAdmin" in user && user.isAdmin;
}
function doSomethings(user: User) {
if (isSuperUser(user)) {
console.log(user.permissions.join(","));
}
}
استنتاجات على النصائح
- أنواع الاسمية : نوع معتم، وحقول خاصة- نوع التباين : أنواع المطابقة، نوع DeepReadonly- الاستثناءات : إما الكائن الدقيق الاحادي الخلية- صقل إبطال : وظائف الصرفة- العمليات غير الآمنة (الوصول إلى رقم قياسي) : إما / ربما الكائنات الدقيقة الاحاديه الخليةغير قابل للتغيير البيانات، وظائف نقية، الكائنات الدقيقة الاحاديه الخلية ... مبروك ، أثبتنا أن FP رائع!الموجودات
- تريد TS تحقيق التوازن بين الصحة والإنتاجية
- يمكن أن تثبت الاختبارات الأمان والصحة لبيانات الاختبار فقط.
- يمكن أن تثبت الأنواع الأمان العام للبرنامج.
- طفرة - سيئة ، حسنا؟
كما توصي DZ باللعب مع Flow بعد تصحيح خطأ بسيط:
declare function log(arg: { name: string, surname?: string }): void;
const person: { name: string } = { name: 'Negasi' };
log(person);
رمز المثال ، حل المشكلة وتحليلها ، روابط مفيدة في المستودع .