From f8531cb7e1a0429aa071c22ede150ba8bb4343f2 Mon Sep 17 00:00:00 2001 From: Msystech Date: Thu, 20 Aug 2026 18:37:57 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EC=86=8C=EC=8A=A4?= =?UTF-8?q?=20=EB=B0=B0=EC=84=A0=EC=9D=B4=20=EC=8B=A4=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=A0=84=EB=B6=80=20=EC=A3=BD=EC=96=B4=20?= =?UTF-8?q?=EC=9E=88=EC=97=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 구조 사용성 조사를 돌렸더니 가장 무거운 것이 사용성이 아니라 버그였다. ## 무슨 일이었나 배선 실행기가 데이터소스를 레거시 클래스명으로 찾고 있었다. if (control.Type == "MDataTable") 그런데 서식을 XML 에서 읽는 순간 LegacyTypeCatalog:45 가 그 이름을 중립 이름 "DataTable" 로 바꿔 둔다. 즉 실사용 서식에서는 이 조건이 한 번도 참이 되지 않는다 — 데이터소스를 한 개도 못 모은다. 결과는 조용하다. 배선을 옳게 걸어도 모든 DataTableField 가 "데이터소스 X 이 이 서식에 없습니다"로 실패해 미리보기와 인쇄에서 빈칸이 되고, 미리보기의 '데이터소스...' 버튼은 영영 회색이다(Names.Count 가 0이라). 사용자 입장에서는 "배선을 걸었는데 값이 안 나온다" 뿐이고 이유를 알 방법이 없다. ## 왜 여태 못 봤나 진단 네 곳이 전부 ControlElement 를 손으로 만들면서 같은 레거시 이름을 썼다 (EditSmoke:395,739 · DialogShots:465 · DbSmoke:2269). 러너의 오타와 진단의 오타가 짝이 맞아 "배선: 서식에서 데이터소스 이름을 모은다" 가 계속 초록이었다. 로더를 우회해 만든 문서로 로더의 계약을 검사하고 있었다 — 검사가 실사용과 다른 세계를 보고 있었다. ## 고친 것 ① 러너가 중립 타입명으로 비교한다. 문자열은 LegacyTypeCatalog.DataTableType 상수 하나로 모았다 — 두 이름이 공존하는 한 각자 적으면 같은 일이 또 난다. ② 진단 네 곳이 같은 상수를 쓰게 했다. ③ 실사용과 같은 경로로 만드는 검사를 새로 넣었다 — 팔레트 경로(AddControlAt("DataTable"))로 데이터소스를 만들고 러너가 그것을 찾는지, 배선이 '이 서식에 없습니다'로 떨어지지 않는지 본다. ## 고치기 전이었으면 잡혔는지 확인했다 러너 한 줄만 옛 비교로 되돌려 돌려 봤다 — 새 검사 2건과 기존 검사 1건이 실패했다. 기존 검사가 실패한 것은 ②로 진단이 더 이상 오타를 복제하지 않기 때문이다. 이 세 줄이 앞으로 같은 어긋남을 막는다. ## 판정 - 단위 시험 388 · edit-smoke 380건 전건 통과(배선 3건 추가) - --dialog-shots 69장 전건 통과 - --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변 (그 게이트는 PageView 직접 렌더라 해석기를 타지 않는다 — 이 수정과 무관하다는 뜻이기도 하다) DB 가 붙은 단말에서 실제 배선이 값을 내는지는 --db-patient 계열로 별도 확인이 필요하다. 이 커밋이 고친 것은 "데이터소스를 찾는 것"까지다. Co-Authored-By: Claude Fable 5 --- .../Serialization/LegacyTypeCatalog.cs | 13 ++++++- src/SheetMe.Designer/Diagnostics/DbSmoke.cs | 2 +- .../Diagnostics/DialogShots.cs | 2 +- src/SheetMe.Designer/Diagnostics/EditSmoke.cs | 35 +++++++++++++++++-- .../Services/MDataTableRunner.cs | 9 ++++- 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/SheetMe.Core/Serialization/LegacyTypeCatalog.cs b/src/SheetMe.Core/Serialization/LegacyTypeCatalog.cs index 0dba75e..56082a3 100644 --- a/src/SheetMe.Core/Serialization/LegacyTypeCatalog.cs +++ b/src/SheetMe.Core/Serialization/LegacyTypeCatalog.cs @@ -14,6 +14,17 @@ public static class LegacyTypeCatalog /// 페이지 루트 클래스 단축명 public const string HostClassName = "MDesignerHost"; + /// + /// 데이터소스의 중립 타입명 — 레거시 클래스명은 MDataTable 이고 여기서 이 이름으로 바뀐다. + /// + /// 상수로 두는 이유. 두 이름이 공존해서 실제로 사고가 났다. + /// 배선 실행기가 레거시 이름(MDataTable)으로 컨트롤을 찾고 있었는데, + /// 서식을 XML 에서 읽으면 그 시점에 이미 중립 이름으로 바뀌어 있어 한 개도 못 찾았다 — + /// 실사용 서식의 데이터소스 배선이 전부 "이 서식에 없습니다"로 죽었다. + /// 문자열을 각자 적는 한 같은 일이 또 난다. + /// + public const string DataTableType = "DataTable"; + /// 페이지 루트 AQN (실샘플 sheetdesign.xml 원문 확인) public const string HostAqn = "M.EMR.UserControl.MDesignerHost, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"; @@ -42,7 +53,7 @@ public static class LegacyTypeCatalog ["MPictureBox"] = "PictureBox", ["MCalcBox"] = "CalcBox", ["MButton"] = "Button", - ["MDataTable"] = "DataTable", + ["MDataTable"] = DataTableType, // Spread 는 읽기 매핑만(렌더) — E_SpdMst 디자인 없이는 신규 생성 불가하므로 쓰기 매핑/팔레트 미등록 ["Spread"] = "Spread", }; diff --git a/src/SheetMe.Designer/Diagnostics/DbSmoke.cs b/src/SheetMe.Designer/Diagnostics/DbSmoke.cs index 9ffe707..0332303 100644 --- a/src/SheetMe.Designer/Diagnostics/DbSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/DbSmoke.cs @@ -2266,7 +2266,7 @@ public static class DbSmoke probeDoc.Pages.Add(probePage); var probeSource = new SheetMe.Core.Models.ControlElement { - Type = "MDataTable", Id = "MDataTable1", + Type = SheetMe.Core.Serialization.LegacyTypeCatalog.DataTableType, Id = "MDataTable1", Bounds = new SheetMe.Core.Models.LayoutRect { X = 0, Y = 0, W = 20, H = 20 }, }; // 스칼라 치환과 DataRow 치환을 한 문장에 넣는다 — 둘은 다른 코드 경로다 diff --git a/src/SheetMe.Designer/Diagnostics/DialogShots.cs b/src/SheetMe.Designer/Diagnostics/DialogShots.cs index d3a0f68..68b0052 100644 --- a/src/SheetMe.Designer/Diagnostics/DialogShots.cs +++ b/src/SheetMe.Designer/Diagnostics/DialogShots.cs @@ -462,7 +462,7 @@ public static class DialogShots { var source = new SheetMe.Core.Models.ControlElement { - Type = "MDataTable", Id = id, + Type = SheetMe.Core.Serialization.LegacyTypeCatalog.DataTableType, Id = id, Bounds = new SheetMe.Core.Models.LayoutRect { X = 0, Y = 0, W = 20, H = 20 }, }; source.Props.SetText("Query", query); diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs index 2800a6f..4eae119 100644 --- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs @@ -392,7 +392,7 @@ public static class EditSmoke } host.Children.Add(new ControlElement { - Type = "MDataTable", Id = "안쪽데이터소스", + Type = SheetMe.Core.Serialization.LegacyTypeCatalog.DataTableType, Id = "안쪽데이터소스", Bounds = new LayoutRect { X = 10, Y = 90, W = 120, H = 40 }, }); boxedPage.Controls.Add(host); @@ -736,7 +736,7 @@ public static class EditSmoke fieldDoc.Pages.Add(fieldPage); var source = new ControlElement { - Type = "MDataTable", Id = "MDataTable1", + Type = SheetMe.Core.Serialization.LegacyTypeCatalog.DataTableType, Id = "MDataTable1", Bounds = new LayoutRect { X = 0, Y = 0, W = 20, H = 20 }, }; source.Props.SetText("Query", "SELECT 'ㄱ' NAM FROM DUAL"); @@ -1112,6 +1112,37 @@ public static class EditSmoke $"ValueText='{choiceRow.ValueText}' IsMixed={choiceRow.IsMixed}"); } + // 15-a000) 데이터소스를 실사용과 같은 경로로 만들었을 때도 러너가 찾는가. + // + // 이 검사가 없어서 배선 전체가 실사용에서 죽어 있는 것을 오래 못 봤다. + // 러너는 레거시 클래스명("MDataTable")으로 컨트롤을 찾고 있었는데, 서식을 XML 에서 읽으면 + // 그 순간 중립 이름("DataTable")으로 바뀌어 있어 한 개도 못 모았다 — + // 모든 DataTableField 가 "이 서식에 없습니다"로 실패해 종이에서 빈칸이 됐다. + // 그런데 진단들이 ControlElement 를 손으로 만들며 같은 레거시 이름을 써서 + // 러너의 오타와 짝이 맞아 전부 초록이었다. 그래서 여기서는 손으로 만들지 않는다. + { + Check("타입명: 레거시 MDataTable 은 중립 DataTable 로 읽힌다", + SheetMe.Core.Serialization.LegacyTypeCatalog.DataTableType == "DataTable"); + + var dsDoc = new DesignerViewModel(new FormDesignDataBusiness().CreateNew()); + dsDoc.AddControlAt("DataTable", new Point(60, 60)); // 팔레트가 쓰는 그 경로 + var made = dsDoc.Pages[0].Controls.Single(c => c.Type == "DataTable"); + made.Model.Props.SetText("Query", "SELECT 1 FROM DUAL"); + + var runner = new Services.MDataTableRunner(dsDoc.Document, null); + Check("배선: 팔레트로 만든 데이터소스를 러너가 찾는다", + runner.Names.Count == 1 && runner.Names[0] == made.Id, + runner.Names.Count == 0 ? "0개 — 러너가 타입명을 잘못 보고 있다" + : string.Join(",", runner.Names)); + + // 이름으로 가리키는 배선이 실제로 그 데이터소스에 닿는가(값은 DB 없이 못 내지만 + // '이 서식에 없습니다'로 떨어지면 안 된다) + var spec = SheetMe.Core.Catalog.DataTableFieldSpec.Parse($"{made.Id}.Rows(0).Item(\"NAM\")"); + var reason = spec is null ? "(형식 파싱 실패)" : runner.Resolve(spec).Text; + Check("배선: '이 서식에 없습니다'로 떨어지지 않는다", + !reason.Contains("이 서식에 없습니다", StringComparison.Ordinal), reason); + } + // 15-a00) 아이콘 기본색이 검정으로 떨어지지 않는다. // // 색도 리소스 키도 안 준 아이콘이 Brushes.Black 으로 칠해지고 있었다. diff --git a/src/SheetMe.Designer/Services/MDataTableRunner.cs b/src/SheetMe.Designer/Services/MDataTableRunner.cs index aada052..cb4be44 100644 --- a/src/SheetMe.Designer/Services/MDataTableRunner.cs +++ b/src/SheetMe.Designer/Services/MDataTableRunner.cs @@ -1,5 +1,6 @@ using SheetMe.Core.Catalog; using SheetMe.Core.Models; +using SheetMe.Core.Serialization; using SheetMe.Data.Stores; namespace SheetMe.Designer.Services; @@ -141,7 +142,13 @@ public sealed class MDataTableRunner : IDataFieldResolver { foreach (var control in controls) { - if (control.Type == "MDataTable") + // 중립 타입명으로 비교한다. 여기가 레거시 클래스명("MDataTable")을 보고 있어서 + // 실사용 서식에서는 데이터소스를 한 개도 못 모았다 — XML 에서 읽는 순간 + // LegacyTypeCatalog 가 이미 "DataTable" 로 바꿔 두기 때문이다. + // 그 결과 모든 DataTableField 배선이 "데이터소스 X 이 이 서식에 없습니다"로 실패해 + // 미리보기·인쇄에서 빈칸이 됐고, 미리보기의 '데이터소스...' 버튼은 영영 회색이었다. + // 진단들이 ControlElement 를 손으로 만들며 같은 레거시 이름을 써서 초록으로 통과하고 있었다. + if (string.Equals(control.Type, LegacyTypeCatalog.DataTableType, StringComparison.Ordinal)) { queries[control.Id] = control.Props.GetText("Query") ?? string.Empty; }