avoid_implementing_value_types 규칙: ==를 오버라이딩하는 클래스를 implements로 구현하지 않기
avoid_implementing_value_types 규칙: ==를 오버라이딩하는 클래스를 implements로 구현하지 않기
avoid_implementing_value_types는 ==를 오버라이딩하는 클래스를 implements로 구현하지 말라고 알려주는 린트(lint) 규칙이에요. implements로 구현하면 ==의 메서드 본문을 상속받지 않아서, ==가 지켜야 하는 계약(equivalence relation)을 따르기가 사실상 불가능해져요.
본문
설명
==를 오버라이딩하는 클래스를 implements로 구현하지 마세요.
== 연산자는 계약상 동치 관계(equivalence relation)여야 해요. 즉 모든 객체 o1과 o2에 대해 대칭적으로, o1 == o2와 o2 == o1이 둘 다 true이거나 둘 다 false여야 해요.
NOTE: Dart에는 진짜 *값 타입(value type)*이 없어서, 대신 ==를 구현하는 클래스를 값 타입을 식별하는 *대리(proxy)*로 간주해요.
implements를 쓰면 ==의 메서드 본문을 상속받지 않아서, ==의 계약을 따르기가 사실상 불가능해져요. ==를 오버라이딩하는 클래스는 보통 mock이나 fake를 만들지 않고도 테스트에서 직접 쓸 수 있는 경우가 많아요. 예를 들어 Size라는 클래스가 있다고 해 볼게요.
class Size {
final int inBytes;
const Size(this.inBytes);
@override
bool operator ==(Object other) => other is Size && other.inBytes == inBytes;
@override
int get hashCode => inBytes.hashCode;
}
BAD:
class CustomSize implements Size {
final int inBytes;
const CustomSize(this.inBytes);
int get inKilobytes => inBytes ~/ 1000;
}
BAD:
import 'package:test/test.dart';
import 'size.dart';
class FakeSize implements Size {
int inBytes = 0;
}
void main() {
test('should not throw on a size >1Kb', () {
expect(() => someFunction(FakeSize()..inBytes = 1001), returnsNormally);
});
}
GOOD:
class ExtendedSize extends Size {
ExtendedSize(int inBytes) : super(inBytes);
int get inKilobytes => inBytes ~/ 1000;
}
GOOD:
import 'package:test/test.dart';
import 'size.dart';
void main() {
test('should not throw on a size >1Kb', () {
expect(() => someFunction(Size(1001)), returnsNormally);
});
}
활성화
avoid_implementing_value_types 규칙을 활성화하려면 analysis_options.yaml 파일의 linter > rules에 avoid_implementing_value_types를 추가해요.
linter:
rules:
- avoid_implementing_value_types
YAML map 문법으로 린터 규칙을 설정한다면 linter > rules에 avoid_implementing_value_types: true를 추가해요.
linter:
rules:
avoid_implementing_value_types: true