refactor: устранить нарушения DRY, SRP, KISS

- DRY: вынести scoped() в общий ScopedKey (был дублирован в PayloadStore и SharedIndex)
- DRY: вынести чтение словарей и перечитывание файлов в ResourceLoader
  (было дублировано в NameDictionary, ToponymDictionary, SystemsConfig)
- DRY: вынести алгоритм Луна в Validators.luhnCheckDigit (Synthetic переиспользует)
- SRP: вынести константы типов ПД из RuleRegistry в PdTypes
- SRP: вынести проверку организаций из NameDictionary в OrganisationDetector
- KISS: словари оставлены статическими (неизменяемые, детерминированные)

Поведение не изменилось: 1840 тестов проходят, NodeLogsLeakTest по-прежнему
падает на тех же 55 задокументированных утечках.
This commit is contained in:
Максименко Никита Владимирович
2026-09-22 22:39:16 +03:00
parent 6b5a7ae35f
commit aa4c926a8f
19 changed files with 457 additions and 349 deletions
+4 -3
View File
@@ -6,6 +6,7 @@ import ru.pdguard.core.PayloadStore;
import ru.pdguard.core.Pipeline;
import ru.pdguard.detect.Span;
import ru.pdguard.detect.NameCascade;
import ru.pdguard.detect.PdTypes;
import ru.pdguard.detect.RuleRegistry;
import ru.pdguard.mask.Masker;
@@ -246,7 +247,7 @@ class BenchmarkTest {
}
for (Span gold : sample.gold()) {
if (!RuleRegistry.FIO.equals(gold.type())) {
if (!PdTypes.FIO.equals(gold.type())) {
continue;
}
goldFioSpans++;
@@ -261,7 +262,7 @@ class BenchmarkTest {
report(title, samples.size(), byType, anyType, goldFioSpans, foundFioSpans,
cleanTexts, cleanTextsWithFalseHit, missedFio, falseHits, overMasked);
Score fio = byType.getOrDefault(RuleRegistry.FIO, new Score());
Score fio = byType.getOrDefault(PdTypes.FIO, new Score());
double falsePositiveRate = cleanTexts == 0 ? 0.0 : (double) cleanTextsWithFalseHit / cleanTexts;
return new Result(fio.f1(), anyType.precision(), anyType.recall(),
falsePositiveRate, foundFioSpans, goldFioSpans);
@@ -304,7 +305,7 @@ class BenchmarkTest {
private static boolean overlappedByFio(Span gold, List<Span> found) {
return found.stream()
.anyMatch(span -> RuleRegistry.FIO.equals(span.type()) && span.overlaps(gold));
.anyMatch(span -> PdTypes.FIO.equals(span.type()) && span.overlaps(gold));
}
private static String fragment(String text, Span span) {
@@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
import ru.pdguard.config.SystemPolicy;
import ru.pdguard.core.PayloadStore;
import ru.pdguard.core.Pipeline;
import ru.pdguard.detect.PdTypes;
import ru.pdguard.detect.RuleRegistry;
import ru.pdguard.mask.Masker;
@@ -123,7 +124,7 @@ class DateAndAddressTest {
@Test
void addressTypesAreConfigurableSeparately() {
SystemPolicy onlyCity = SystemPolicy.forTypes(RuleRegistry.ADDRESS_CITY);
SystemPolicy onlyCity = SystemPolicy.forTypes(PdTypes.ADDRESS_CITY);
String masked = pipeline.process("г. Москва, ул. Тверская, д. 7", "addr-1", onlyCity);
assertFalse(masked.contains("Москва"), masked);
+2 -1
View File
@@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
import ru.pdguard.config.SystemPolicy;
import ru.pdguard.core.PayloadStore;
import ru.pdguard.core.Pipeline;
import ru.pdguard.detect.PdTypes;
import ru.pdguard.detect.RuleRegistry;
import ru.pdguard.mask.Masker;
@@ -114,7 +115,7 @@ class PipelineTest {
@Test
void systemPolicyDisablesSelectedTypes() {
Pipeline pipeline = pipeline();
SystemPolicy onlyEmail = SystemPolicy.forTypes(RuleRegistry.EMAIL);
SystemPolicy onlyEmail = SystemPolicy.forTypes(PdTypes.EMAIL);
String masked = pipeline.process("Карта " + VALID_CARD + ", почта ivan@mail.ru", "policy-1", onlyEmail);
assertTrue(masked.contains(VALID_CARD), "карта не должна маскироваться этой системой: " + masked);
@@ -5,6 +5,7 @@ import ru.pdguard.config.SystemPolicy;
import ru.pdguard.core.PayloadStore;
import ru.pdguard.core.Pipeline;
import ru.pdguard.detect.NameCascade;
import ru.pdguard.detect.PdTypes;
import ru.pdguard.detect.RuleRegistry;
import ru.pdguard.detect.Span;
import ru.pdguard.mask.Masker;
@@ -34,7 +35,7 @@ class TwoModelCascadeTest {
for (Span s : spans) {
System.out.println(" -> " + s.type() + " [" + text.substring(s.start(), s.end()) + "]");
}
assertTrue(spans.stream().anyMatch(s -> s.type().equals(RuleRegistry.FIO)), "должно найти ФИО");
assertTrue(spans.stream().anyMatch(s -> s.type().equals(PdTypes.FIO)), "должно найти ФИО");
}
@Test
@@ -45,7 +46,7 @@ class TwoModelCascadeTest {
for (Span s : spans) {
System.out.println(" -> " + s.type() + " [" + text.substring(s.start(), s.end()) + "]");
}
assertTrue(spans.stream().noneMatch(s -> s.type().equals(RuleRegistry.FIO)),
assertTrue(spans.stream().noneMatch(s -> s.type().equals(PdTypes.FIO)),
"известная личность не должна маскироваться");
}
}