对 TypeScript 的接口和 Class 编码指南感到困惑
Confused about the Interface and Class coding guidelines for TypeScript
我通读了TypeScript Coding guidelines
我发现这个说法相当令人费解:
Do not use "I" as a prefix for interface names
我的意思是,如果没有 "I" 前缀
,这样的事情就没有多大意义
class Engine implements IEngine
我是不是遗漏了什么明显的东西?
我不太明白的另一件事是:
Classes
For consistency, do not use classes in the core compiler pipeline. Use
function closures instead.
这是否说明我根本不应该使用 类?
希望有人能帮我解决:)
关于您引用的 link 的说明:
This is the documentation about the style of the code for TypeScript, and not a style guideline for how to implement your project.
如果使用 I
前缀对您和您的团队有意义,请使用它(我这样做)。
如果没有,也许 SomeThing
(接口)的 Java 风格和 SomeThingImpl
(实现)然后一定要使用它。
当 team/company 发布 framework/compiler/tool-set 时,他们已经有了一些经验和最佳实践。他们将其作为指南分享。准则是建议。如果你不喜欢任何一个,你可以忽略它们。
编译器仍然会编译 你的 代码。
虽然when in Rome...
这就是我的看法,为什么 TypeScript 团队不建议使用 I
-前缀接口。
原因 #1 Hungarian notation 的时代已经过去了
I
-prefix-for-interface 支持者的主要论点是前缀有助于立即理解(查看)类型是否是接口。前缀有助于立即 grokking(偷看)的声明是对 Hungarian notation 的呼吁。 I
接口名称前缀,C
表示class,A
表示抽象class,s
表示字符串变量,c
表示const 变量,i
表示整型变量。我同意这样的名称装饰可以为您提供类型信息,而无需将鼠标悬停在标识符上或通过 hot-key 导航到类型定义。这个微小的好处被 Hungarian notation 缺点和下面提到的其他原因所抵消。现代框架中不使用匈牙利符号。由于历史原因 (COM),C# 的接口有 I
前缀(这是 C# 中唯一的前缀)。回想起来,一位 .NET 架构师 (Brad Abrams) 认为 不使用 I
前缀 会更好。 TypeScript 是 COM-legacy-free 因此它没有 I
-prefix-for-interface 规则。
原因#2 I
-前缀违反封装原则
假设您得到了一些 black-box。您将获得一些类型引用,允许您与该框进行交互。你不应该关心它是一个接口还是一个 class。您只需使用它的接口部分。要求知道它是什么(接口,具体实现或抽象class)是违反封装的。
示例:假设您需要在代码中修复 API Design Myth: Interface as Contract,例如删除 ICar
接口并使用 Car
base-class 代替。然后,您需要在所有消费者中执行此类替换。 I
-prefix 导致消费者对 black-box 实现细节的隐式依赖。
原因 #3 防止错误命名
开发者懒得好好想名字。命名是Two Hard Things in Computer Science之一。当开发人员需要提取接口时,只需将字母 I
添加到 class 名称即可轻松获得接口名称。不允许接口使用 I
前缀会迫使开发人员绞尽脑汁为接口选择合适的名称。选择的名称不仅要在前缀上有所不同,而且要强调意图差异。
抽象案例:您应该不不定义ICar
接口和关联的Car
class。 Car
是一种抽象,它应该是用于合约的。实现应具有描述性的、独特的名称,例如SportsCar, SuvCar, HollowCar
.
很好的例子:WpfeServerAutosuggestManager implements AutosuggestManager
, FileBasedAutosuggestManager implements AutosuggestManager
.
错误示例:AutosuggestManager implements IAutosuggestManager
.
原因 #4 正确选择名字可以预防 API Design Myth: Interface as Contract。
在我的实践中,我遇到很多人在具有 Car implements ICar
命名方案的单独界面中不加考虑地复制 class 的界面部分。在单独的接口类型中复制 class 的接口部分不会神奇地将其转换为抽象。您仍将获得具体的实现,但具有重复的接口部分。如果你的抽象不是很好,复制接口部分无论如何都不会改进它。提取抽象是一项艰苦的工作。
注意:在 TS 中,您不需要单独的接口来模拟 classes 或重载功能。
您可以使用 TypeScript utility types,而不是创建一个单独的接口来描述 class 的 public 成员。例如。 Required<T>
构造一个类型,该类型由 T
.
类型的所有 public 个成员组成
export class SecurityPrincipalStub implements Required<SecurityPrincipal> {
public isFeatureEnabled(entitlement: Entitlement): boolean {
return true;
}
public isWidgetEnabled(kind: string): boolean {
return true;
}
public areAdminToolsEnabled(): boolean {
return true;
}
}
如果你想构造一个不包括一些 public 成员的类型,那么你可以使用 combination of Omit and Exclude.
作为接口的类型是一个实现细节。实施细节应隐藏在 API:s 中。这就是为什么你应该避免 I
.
你应该避免prefix
和suffix
。这些都是错误的:
ICar
CarInterface
你应该做的是在 API 中使一个漂亮的名字可见,并在实现中隐藏一个实现细节。这就是为什么我建议:
Car
- 在 API. 中公开的接口
CarImpl
- API 的实现,对消费者隐藏。
我发现 @stanislav-berkov 是对 OP 问题的一个很好的回答。我只会分享我的 2 美分补充说,最终取决于你的 Team/Department/Company/Whatever 达成共识并设置自己的 rules/guidelines 来跟进。
只要可能和需要,坚持标准 and/or 约定是一种很好的做法,它可以使事情更容易理解。另一方面,我确实认为我们仍然可以自由选择编写代码的方式。
从情感方面考虑一下,我们编写代码的方式或我们的编码风格反映了我们的个性,在某些情况下甚至反映了我们的心情。这就是让我们人类而不只是编码机器遵守规则的原因。 我相信编码可以成为一门手艺,而不仅仅是工业化的过程。
我个人非常喜欢通过添加 -able 后缀将名词变成形容词的想法。听起来很不合适,但我喜欢它!
interface Walletable {
inPocket:boolean
cash:number
}
export class Wallet implements Walletable {
//...
}
}
我正在尝试与其他答案类似的模式,但导出一个将具体 class 实例化为接口类型的函数,如下所示:
export interface Engine {
rpm: number;
}
class EngineImpl implements Engine {
constructor() {
this.rpm = 0;
}
}
export const createEngine = (): Engine => new EngineImpl();
在这种情况下,永远不会导出具体实现。
我想添加一个 Props
后缀。
interface FormProps {
some: string;
}
const Form:VFC<FormProps> = (props) => {
...
}
Typescript 文档中建议的指南不是针对使用 typescript 的人,而是针对为 typescript 项目做出贡献的人。如果您阅读页面开头的详细信息,它会清楚地定义谁应该使用该指南。这是指南的 link。
Typescript guidelines
总而言之,作为开发人员,您可以按照自己认为合适的方式命名界面。
我通读了TypeScript Coding guidelines
我发现这个说法相当令人费解:
Do not use "I" as a prefix for interface names
我的意思是,如果没有 "I" 前缀
,这样的事情就没有多大意义class Engine implements IEngine
我是不是遗漏了什么明显的东西?
我不太明白的另一件事是:
Classes
For consistency, do not use classes in the core compiler pipeline. Use function closures instead.
这是否说明我根本不应该使用 类?
希望有人能帮我解决:)
关于您引用的 link 的说明:
This is the documentation about the style of the code for TypeScript, and not a style guideline for how to implement your project.
如果使用 I
前缀对您和您的团队有意义,请使用它(我这样做)。
如果没有,也许 SomeThing
(接口)的 Java 风格和 SomeThingImpl
(实现)然后一定要使用它。
当 team/company 发布 framework/compiler/tool-set 时,他们已经有了一些经验和最佳实践。他们将其作为指南分享。准则是建议。如果你不喜欢任何一个,你可以忽略它们。 编译器仍然会编译 你的 代码。 虽然when in Rome...
这就是我的看法,为什么 TypeScript 团队不建议使用 I
-前缀接口。
原因 #1 Hungarian notation 的时代已经过去了
I
-prefix-for-interface 支持者的主要论点是前缀有助于立即理解(查看)类型是否是接口。前缀有助于立即 grokking(偷看)的声明是对 Hungarian notation 的呼吁。 I
接口名称前缀,C
表示class,A
表示抽象class,s
表示字符串变量,c
表示const 变量,i
表示整型变量。我同意这样的名称装饰可以为您提供类型信息,而无需将鼠标悬停在标识符上或通过 hot-key 导航到类型定义。这个微小的好处被 Hungarian notation 缺点和下面提到的其他原因所抵消。现代框架中不使用匈牙利符号。由于历史原因 (COM),C# 的接口有 I
前缀(这是 C# 中唯一的前缀)。回想起来,一位 .NET 架构师 (Brad Abrams) 认为 不使用 I
前缀 会更好。 TypeScript 是 COM-legacy-free 因此它没有 I
-prefix-for-interface 规则。
原因#2 I
-前缀违反封装原则
假设您得到了一些 black-box。您将获得一些类型引用,允许您与该框进行交互。你不应该关心它是一个接口还是一个 class。您只需使用它的接口部分。要求知道它是什么(接口,具体实现或抽象class)是违反封装的。
示例:假设您需要在代码中修复 API Design Myth: Interface as Contract,例如删除 ICar
接口并使用 Car
base-class 代替。然后,您需要在所有消费者中执行此类替换。 I
-prefix 导致消费者对 black-box 实现细节的隐式依赖。
原因 #3 防止错误命名
开发者懒得好好想名字。命名是Two Hard Things in Computer Science之一。当开发人员需要提取接口时,只需将字母 I
添加到 class 名称即可轻松获得接口名称。不允许接口使用 I
前缀会迫使开发人员绞尽脑汁为接口选择合适的名称。选择的名称不仅要在前缀上有所不同,而且要强调意图差异。
抽象案例:您应该不不定义ICar
接口和关联的Car
class。 Car
是一种抽象,它应该是用于合约的。实现应具有描述性的、独特的名称,例如SportsCar, SuvCar, HollowCar
.
很好的例子:WpfeServerAutosuggestManager implements AutosuggestManager
, FileBasedAutosuggestManager implements AutosuggestManager
.
错误示例:AutosuggestManager implements IAutosuggestManager
.
原因 #4 正确选择名字可以预防 API Design Myth: Interface as Contract。
在我的实践中,我遇到很多人在具有 Car implements ICar
命名方案的单独界面中不加考虑地复制 class 的界面部分。在单独的接口类型中复制 class 的接口部分不会神奇地将其转换为抽象。您仍将获得具体的实现,但具有重复的接口部分。如果你的抽象不是很好,复制接口部分无论如何都不会改进它。提取抽象是一项艰苦的工作。
注意:在 TS 中,您不需要单独的接口来模拟 classes 或重载功能。
您可以使用 TypeScript utility types,而不是创建一个单独的接口来描述 class 的 public 成员。例如。 Required<T>
构造一个类型,该类型由 T
.
export class SecurityPrincipalStub implements Required<SecurityPrincipal> {
public isFeatureEnabled(entitlement: Entitlement): boolean {
return true;
}
public isWidgetEnabled(kind: string): boolean {
return true;
}
public areAdminToolsEnabled(): boolean {
return true;
}
}
如果你想构造一个不包括一些 public 成员的类型,那么你可以使用 combination of Omit and Exclude.
作为接口的类型是一个实现细节。实施细节应隐藏在 API:s 中。这就是为什么你应该避免 I
.
你应该避免prefix
和suffix
。这些都是错误的:
ICar
CarInterface
你应该做的是在 API 中使一个漂亮的名字可见,并在实现中隐藏一个实现细节。这就是为什么我建议:
Car
- 在 API. 中公开的接口
CarImpl
- API 的实现,对消费者隐藏。
我发现 @stanislav-berkov 是对 OP 问题的一个很好的回答。我只会分享我的 2 美分补充说,最终取决于你的 Team/Department/Company/Whatever 达成共识并设置自己的 rules/guidelines 来跟进。
只要可能和需要,坚持标准 and/or 约定是一种很好的做法,它可以使事情更容易理解。另一方面,我确实认为我们仍然可以自由选择编写代码的方式。
从情感方面考虑一下,我们编写代码的方式或我们的编码风格反映了我们的个性,在某些情况下甚至反映了我们的心情。这就是让我们人类而不只是编码机器遵守规则的原因。 我相信编码可以成为一门手艺,而不仅仅是工业化的过程。
我个人非常喜欢通过添加 -able 后缀将名词变成形容词的想法。听起来很不合适,但我喜欢它!
interface Walletable {
inPocket:boolean
cash:number
}
export class Wallet implements Walletable {
//...
}
}
我正在尝试与其他答案类似的模式,但导出一个将具体 class 实例化为接口类型的函数,如下所示:
export interface Engine {
rpm: number;
}
class EngineImpl implements Engine {
constructor() {
this.rpm = 0;
}
}
export const createEngine = (): Engine => new EngineImpl();
在这种情况下,永远不会导出具体实现。
我想添加一个 Props
后缀。
interface FormProps {
some: string;
}
const Form:VFC<FormProps> = (props) => {
...
}
Typescript 文档中建议的指南不是针对使用 typescript 的人,而是针对为 typescript 项目做出贡献的人。如果您阅读页面开头的详细信息,它会清楚地定义谁应该使用该指南。这是指南的 link。 Typescript guidelines
总而言之,作为开发人员,您可以按照自己认为合适的方式命名界面。