質問編集履歴

6

変更

2017/01/26 16:05

投稿

syogakusya
syogakusya

スコア67

test CHANGED
File without changes
test CHANGED
@@ -4,14 +4,14 @@
4
4
 
5
5
  ```C#
6
6
 
7
- #region インターフェース
8
-
9
7
 
10
8
 
11
9
  // データを取得
12
10
 
13
11
  interface IGetData<out T>
14
12
 
13
+ {
14
+
15
15
  Tuple<bool, TParsed> ParseData(TRaw data);
16
16
 
17
17
  }
@@ -22,8 +22,6 @@
22
22
 
23
23
  interface IValidateData<in T>
24
24
 
25
-
26
-
27
25
  {
28
26
 
29
27
  // 必ずGetできる。失敗するのは例外的状況
@@ -78,113 +76,605 @@
78
76
 
79
77
 
80
78
 
79
+ // アプリケーション例外のベースとなる抽象例外クラス
80
+
81
+ public abstract class MyNantokaApplicationException : Exception
82
+
83
+ {
84
+
85
+ public IFixedLog Log { get; }
86
+
87
+
88
+
89
+ protected MyNantokaApplicationException(IFixedLog log)
90
+
91
+ : base()
92
+
93
+ {
94
+
95
+ this.Log = log;
96
+
97
+ }
98
+
99
+
100
+
101
+ //...
102
+
103
+ }
104
+
105
+
106
+
107
+ // GetDataがGetに失敗したときに出す例外とする
108
+
109
+ public class GetDataException : MyNantokaApplicationException
110
+
111
+ {
112
+
113
+ //...
114
+
115
+ }
116
+
117
+
118
+
119
+ // SendDataがSendに失敗したときに出す例外とする
120
+
121
+ public class SendDataException : MyNantokaApplicationException
122
+
123
+ {
124
+
125
+ //...
126
+
127
+ }
128
+
129
+
130
+
131
+ // MapDataがMapに失敗したときに出す例外とする
132
+
133
+ // 失敗したら予期しないエラーのほうがいい?
134
+
135
+ public class MapException : MyNantokaApplicationException
136
+
137
+ {
138
+
139
+ //...
140
+
141
+ }
142
+
143
+
144
+
145
+ // GetParseSendFlowがデータの変換に失敗したときに出す例外とする
146
+
147
+ public class ParseFailedException : MyNantokaApplicationException
148
+
149
+ {
150
+
151
+ //...
152
+
153
+ }
154
+
155
+
156
+
157
+ // オブジェクトの生成に失敗したときに出す例外とする
158
+
159
+ public class ConstructorException : MyNantokaApplicationException
160
+
161
+ {
162
+
163
+ //...
164
+
165
+ }
166
+
167
+
168
+
169
+ class Program
170
+
171
+ {
172
+
173
+ static int Main()
174
+
175
+ {
176
+
177
+ var logger = new FixedLoggerToFile();
178
+
179
+ try
180
+
181
+ {
182
+
183
+ // 本当はリソースを保持したらだめ
184
+
185
+ using (var sender = new PlainFileSender(@"C:\...")) // Send)
186
+
187
+ {
188
+
189
+ var getter = new FileDataGetter("..."); // Get
190
+
191
+
192
+
193
+ var validator = new XXXXLogDataValidatorWithLog(logger);
194
+
195
+ var mapper = new XXXSimpleMapper();
196
+
197
+ var parser = new CollectionParser<string, Something>(validator, mapper, logger); // Parse
198
+
199
+
200
+
201
+ var test = new GetParseSendFlow<IEnumerable<string>, IEnumerable<Something>>(getter, parser, sender); // 合体
202
+
203
+ test.Do(); // 処理実行
204
+
205
+ }
206
+
207
+
208
+
209
+ return 0; // 正常終了
210
+
211
+ }
212
+
213
+ catch (MyNantokaApplicationException exception) // アプリケーション例外の場合は
214
+
215
+ {
216
+
217
+ var log = exception.Log; // 例外からLogを取り出す
218
+
219
+ if (log != null)
220
+
221
+ logger.Log(log); // ログを出力
222
+
223
+ return -1;
224
+
225
+ }
226
+
227
+ catch // 予期しない例外の場合は
228
+
229
+ {
230
+
231
+ var log = UnexpectedErrorLog.Instance;
232
+
233
+ logger.Log(log);
234
+
235
+ return -1;
236
+
237
+ }
238
+
239
+ }
240
+
241
+ }
242
+
243
+
244
+
245
+ class GetParseSendFlow<TData, TParsed>
246
+
247
+ {
248
+
249
+ //...
250
+
251
+
252
+
253
+ public GetParseSendFlow(IGetData<TData> getter, IParseData<TData, TParsed> parser, ISendData<TParsed> sender)
254
+
255
+ {
256
+
257
+ //...
258
+
259
+ }
260
+
261
+
262
+
263
+ public void Do()
264
+
265
+ {
266
+
267
+ var raw = _getter.GetData(); // GetDataExceptionが起きても何もできないので素通し
268
+
269
+
270
+
271
+ var parseResult = _parser.ParseData(raw); // ParseDataは例外を出さない
272
+
273
+
274
+
275
+ if (!parseResult.Item1)// parseが失敗した場合は
276
+
277
+ {
278
+
279
+ //var log = ParseFailedAndInterruptErrorLog.Instance; // お手上げなのでぶん投げる
280
+
281
+ throw new ParsedFailedException(null); // 要件を満たすためにとりあえずnull
282
+
283
+ }
284
+
285
+
286
+
287
+ _sender.SendData(parseResult.Item2); // SendDataExceptionが起きても何もできないので素通し
288
+
289
+ }
290
+
291
+ }
292
+
293
+
294
+
295
+ // ファイルから文字列のコレクションを取得するIGetDataの実装
296
+
297
+ public class FileDataGetter : IGetData<IEnumerable<string>>
298
+
299
+ {
300
+
301
+ //...
302
+
303
+
304
+
305
+ public FileDataGetter(string path /** , string ... **/)
306
+
307
+ {
308
+
309
+ _path = path;
310
+
311
+ }
312
+
313
+
314
+
315
+ public IEnumerable<string> GetData()
316
+
317
+ {
318
+
319
+ StreamReader reader = null;
320
+
321
+ NetworkConnection networkConnection = null;
322
+
323
+
324
+
325
+ try
326
+
327
+ {
328
+
329
+ try
330
+
331
+ {
332
+
333
+ // ネットワークに接続
334
+
335
+ }
336
+
337
+ catch
338
+
339
+ {
340
+
341
+ throw new GetDataException(ConnectToNetworkForFileErrorLog.Instance); // GetDataExceptionをスロー
342
+
343
+ }
344
+
345
+
346
+
347
+ try
348
+
349
+ {
350
+
351
+ // ファイルオープン
352
+
353
+ reader = new StreamReader(_path);
354
+
355
+ }
356
+
357
+ catch
358
+
359
+ {
360
+
361
+ throw new GetDataException(FileOpenErrorLog.Instance); // GetDataExceptionをスロー
362
+
363
+ }
364
+
365
+
366
+
367
+ var list = new List<string>();
368
+
369
+ //...
370
+
371
+ return list;
372
+
373
+ }
374
+
375
+ finally
376
+
377
+ {
378
+
379
+ reader?.Dispose();
380
+
381
+ networkConnection?.Dispose();
382
+
383
+ }
384
+
385
+ }
386
+
387
+ }
388
+
389
+
390
+
391
+ // コレクションのデータを変換するIParseDataの実装
392
+
393
+ class CollectionParser<TRawItem, TMappedItem> : IParseData<IEnumerable<TRawItem>, IEnumerable<TMappedItem>>
394
+
395
+ {
396
+
397
+ //...
398
+
399
+
400
+
401
+ public CollectionParser(
402
+
403
+ IValidateData<TRawItem> validator,
404
+
405
+ IMapData<TRawItem, TMappedItem> mapper,
406
+
407
+ IFixedLogger logger)
408
+
409
+ {
410
+
411
+ this._validator = validator;
412
+
413
+ this._mapper = mapper;
414
+
415
+ this._logger = logger;
416
+
417
+ }
418
+
419
+
420
+
421
+ public Tuple<bool, IEnumerable<TMappedItem>> ParseData(IEnumerable<TRawItem> rawItems)
422
+
423
+ {
424
+
425
+ // 変換の成否に関する戻り値設定に使う
426
+
427
+ var inputItemsCount = 0;
428
+
429
+
430
+
431
+ // 検証と変換に成功したもののみで絞込
432
+
433
+ var parseResult = rawItems.Select(
434
+
435
+ rawItem =>
436
+
437
+ {
438
+
439
+ // 生データコレクションカウント
440
+
441
+ inputItemsCount++;
442
+
443
+
444
+
445
+ if (_validator.ValidateData(rawItem)) // 検証してみる
446
+
447
+ {
448
+
449
+ // 検証OKならマッピングしてみる
450
+
451
+ try
452
+
453
+ {
454
+
455
+ var mapped = this._mapper.MapData(rawItem);
456
+
457
+ return new { Data = mapped, Result = true };
458
+
459
+ }
460
+
461
+ catch (MapException ex)
462
+
463
+ {
464
+
465
+ _logger.Log(ex.Log); // 失敗したらエラーログ
466
+
467
+ }
468
+
469
+ }
470
+
471
+ // 検証失敗やマッピングで例外の場合は失敗を返す
472
+
473
+ return new { Data = default(TMappedItem), Result = false };
474
+
475
+ }).Where(mappingResult => mappingResult.Result).Select(mappingResult => mappingResult.Data).ToList();
476
+
477
+
478
+
479
+ // 得られたコレクションの長さで判定
480
+
481
+ return new Tuple<bool, IEnumerable<TMappedItem>>(parseResult.Count == inputItemsCount, parseResult);
482
+
483
+ }
484
+
485
+ }
486
+
487
+
488
+
489
+ // 特定のソフトが出力するログを検証し、エラーログも出力するIValidateの実装
490
+
491
+ class XXXXLogDataValidatorWithLog : IValidateData<string>
492
+
493
+ {
494
+
495
+ private readonly IFixedLogger _logger;
496
+
497
+
498
+
499
+ public XXXXLogDataValidatorWithLog(IFixedLogger logger)
500
+
501
+ {
502
+
503
+ this._logger = logger;
504
+
505
+ }
506
+
507
+
508
+
509
+ public bool ValidateData(string data)
510
+
511
+ {
512
+
513
+ var result = data == string.Empty;
514
+
515
+ // 失敗したらログを出力
516
+
517
+ if (!result)
518
+
519
+ {
520
+
521
+ _logger.Log(UnavailableDataDetectedErrorLog.GetInstance(data, "文字列が空文字"));
522
+
523
+ }
524
+
525
+
526
+
527
+ return result;
528
+
529
+ }
530
+
531
+ }
532
+
533
+
534
+
535
+ // 特定のソフトが出力するログをオブジェクトに変換するIMapの実装
536
+
537
+ class XXXSimpleMapper : IMapData<string, Something>
538
+
539
+ {
540
+
541
+ public Something MapData(string raw)
542
+
543
+ {
544
+
545
+ string name;
546
+
547
+ string message;
548
+
549
+
550
+
551
+ try
552
+
553
+ {
554
+
555
+ name = raw.Substring(0, 10).TrimStart();
556
+
557
+ }
558
+
559
+ catch
560
+
561
+ {
562
+
563
+ // ここや
564
+
565
+ throw new SimpleMapExeption("名前が変換できない", raw);
566
+
567
+ }
568
+
569
+
570
+
571
+ try
572
+
573
+ {
574
+
575
+ message = raw.Substring(10);
576
+
577
+ }
578
+
579
+ catch
580
+
581
+ {
582
+
583
+ // ここは、MapExceptionではなく、予期しないエラーになるべきか?
584
+
585
+ // あるいは、ここでログを出力する?
586
+
587
+ // その場合返すものがなくなる
588
+
589
+ // IParseDataにかえて、ここでログをはきfalseを返す?
590
+
591
+ // そもそもIMapいる?
592
+
593
+ throw new SimpleMapExeption("メッセージが変換できない", raw);
594
+
595
+ }
596
+
597
+
598
+
599
+ return new Something(name, message);
600
+
601
+ }
602
+
603
+ }
604
+
605
+
606
+
81
607
  #endregion
82
608
 
83
609
 
84
610
 
85
- #region 例外
86
-
87
-
88
-
89
- // アプリケーション例外のベースとなる抽象例外クラス
90
-
91
- public abstract class MyNantokaApplicationException : Exception
92
-
93
- {
94
-
95
- public IFixedLog Log { get; }
96
-
97
-
98
-
99
- protected MyNantokaApplicationException(IFixedLog log)
100
-
101
- : base()
102
-
103
- {
104
-
105
- this.Log = log;
106
-
107
- }
108
-
109
-
110
-
111
- //...
112
-
113
- }
114
-
115
-
116
-
117
- // アプリケーションを強制終了するほどの致命的なエラーを表す
118
-
119
- public class FatalErrorException : MyNantokaApplicationException
120
-
121
- {
122
-
123
- public FatalErrorException(IFixedLog log)
124
-
125
- : base(log)
126
-
127
- {
128
-
129
- }
130
-
131
-
132
-
133
- //...
134
-
135
- }
136
-
137
-
138
-
139
- // GetDataがGetに失敗したときに出す例外とする
140
-
141
- public class GetDataException : MyNantokaApplicationException
142
-
143
- {
144
-
145
- public GetDataException(IFixedLog log)
146
-
147
- : base(log)
148
-
149
- {
150
-
151
- }
152
-
153
- }
154
-
155
-
156
-
157
- // SendDataがSendに失敗したときに出す例外とする
158
-
159
- public class SendDataException : MyNantokaApplicationException
160
-
161
- {
162
-
163
- public SendDataException(IFixedLog log)
164
-
165
- : base(log)
166
-
167
- {
168
-
169
- }
170
-
171
- }
172
-
173
-
174
-
175
- // MapDataがMapに失敗したときに出す例外とする
176
-
177
- // 失敗したら予期しないエラーのほうがいい?
178
-
179
- public class MapException : MyNantokaApplicationException
180
-
181
- {
182
-
183
- public MapException(IFixedLog log)
184
-
185
- : base(log)
186
-
187
- {
611
+ #region ISendDataの実体
612
+
613
+ // データをファイルに送信するISendDataの実装 リソースを保持するのでよくない
614
+
615
+ class PlainFileSender : ISendData<IEnumerable<Something>>, IDisposable
616
+
617
+ {
618
+
619
+ private readonly StreamWriter _writer;
620
+
621
+
622
+
623
+ public PlainFileSender(string path)
624
+
625
+ {
626
+
627
+ try
628
+
629
+ {
630
+
631
+ this._writer = new StreamWriter(path, true);
632
+
633
+ }
634
+
635
+ catch
636
+
637
+ {
638
+
639
+ throw new ConstructorException(FileOpenErrorLog.Instance);
640
+
641
+ }
642
+
643
+
644
+
645
+ }
646
+
647
+
648
+
649
+ public void SendData(IEnumerable<Something> data)
650
+
651
+ {
652
+
653
+ try
654
+
655
+ {
656
+
657
+ // データをテキストファイルに追加
658
+
659
+ }
660
+
661
+ catch
662
+
663
+ {
664
+
665
+ throw new SendDataException(FileAppendErrorLog.Instance);
666
+
667
+ }
668
+
669
+ }
670
+
671
+
672
+
673
+ public void Dispose()
674
+
675
+ {
676
+
677
+ this._writer?.Dispose();
188
678
 
189
679
  }
190
680
 
@@ -194,584 +684,8 @@
194
684
 
195
685
  #endregion
196
686
 
197
-
198
-
199
- #region エントリポイント
200
-
201
-
202
-
203
- class Program
204
-
205
- {
206
-
207
- static int Main()
208
-
209
- {
210
-
211
- var logger = new FixedLoggerToFile();
212
-
213
- try
214
-
215
- {
216
-
217
- // このコンストラクタは例外的状況下で失敗するが、FatalErrorをなげないので予期しない例外になる!
218
-
219
- using (var sender = new PlainFileSender(@"C:\...")) // Send)
220
-
221
- {
222
-
223
- var getter = new FileDataGetter("..."); // Get
224
-
225
-
226
-
227
- var validator = new XXXXLogDataValidatorWithLog(logger);
228
-
229
- var mapper = new XXXSimpleMapper();
230
-
231
- var parser = new CollectionParser<string, Something>(validator, mapper, logger); // Parse
232
-
233
-
234
-
235
- var test = new GetParseSendFlow<IEnumerable<string>, IEnumerable<Something>>(getter, parser, sender); // 合体
236
-
237
- test.Do(); // 処理実行
238
-
239
- }
240
-
241
-
242
-
243
- return 0; // 正常終了
244
-
245
- }
246
-
247
- catch (FatalErrorException exception) // 致命的なエラーによる強制終了の場合は
248
-
249
- {
250
-
251
- var log = exception.Log; // 例外からLogを取り出す
252
-
253
- if (log != null)
254
-
255
- {
256
-
257
- logger.Log(log); // ログを出力
258
-
259
- }
260
-
261
-
262
-
263
- return -1;
264
-
265
- }
266
-
267
- catch // 予期しない例外の場合は
268
-
269
- {
270
-
271
- var log = UnexpectedErrorLog.Instance;
272
-
273
- logger.Log(log);
274
-
275
- return -1;
276
-
277
- }
278
-
279
- }
280
-
281
- }
282
-
283
- #endregion
284
-
285
-
286
-
287
- #region 元Testクラス
288
-
289
- class GetParseSendFlow<TData, TParsed>
290
-
291
- {
292
-
293
- //...
294
-
295
-
296
-
297
- public GetParseSendFlow(IGetData<TData> getter, IParseData<TData, TParsed> parser, ISendData<TParsed> sender)
298
-
299
- {
300
-
301
- _getter = getter;
302
-
303
- _parser = parser;
304
-
305
- _sender = sender;
306
-
307
- }
308
-
309
-
310
-
311
- public void Do()
312
-
313
- {
314
-
315
- var raw = default(TData);
316
-
317
-
318
-
319
- try
320
-
321
- {
322
-
323
- raw = _getter.GetData();
324
-
325
- }
326
-
327
- catch (GetDataException exception)
328
-
329
- {
330
-
331
- var log = exception.Log; // GetDataExceptionから実際に失敗した理由のLogを取り出す
332
-
333
- throw new FatalErrorException(log); // Get失敗は致命的なエラー扱いとして、そのログを入れてスロー
334
-
335
- }
336
-
337
-
338
-
339
- var parseResult = _parser.ParseData(raw); // ParseDataは例外を出さない
340
-
341
-
342
-
343
- if (!parseResult.Item1)// parseが失敗した場合は
344
-
345
- {
346
-
347
- //var log = ParseFailedAndInterruptErrorLog.Instance; // 終了理由を入れてスロー
348
-
349
- throw new FatalErrorException(null); // 要件を満たすためにとりあえずnull
350
-
351
- }
352
-
353
-
354
-
355
- try
356
-
357
- {
358
-
359
- _sender.SendData(parseResult.Item2);
360
-
361
- }
362
-
363
- catch (SendDataException exception) //
364
-
365
- {
366
-
367
- var log = exception.Log;
368
-
369
- throw new FatalErrorException(log); // Send失敗は致命的なエラー扱いとして、そのログを入れてスロー
370
-
371
- }
372
-
373
- }
374
-
375
- }
376
-
377
-
378
-
379
- #endregion
380
-
381
-
382
-
383
- #region IGetDataの実体
384
-
385
- // ファイルから文字列のコレクションを取得するIGetDataの実装
386
-
387
- public class FileDataGetter : IGetData<IEnumerable<string>>
388
-
389
- {
390
-
391
- //...
392
-
393
-
394
-
395
- public FileDataGetter(string path /** , string ... **/)
396
-
397
- {
398
-
399
- _path = path;
400
-
401
- }
402
-
403
-
404
-
405
- public IEnumerable<string> GetData()
406
-
407
- {
408
-
409
- StreamReader reader = null;
410
-
411
- NetworkConnection networkConnection = null;
412
-
413
-
414
-
415
- try
416
-
417
- {
418
-
419
- try
420
-
421
- {
422
-
423
- // ネットワークに接続
424
-
425
- }
426
-
427
- catch
428
-
429
- {
430
-
431
- throw new GetDataException(ConnectToNetworkForFileErrorLog.Instance); // GetDataExceptionをスロー
432
-
433
- }
434
-
435
-
436
-
437
- try
438
-
439
- {
440
-
441
- // ファイルオープン
442
-
443
- reader = new StreamReader(_path);
444
-
445
- }
446
-
447
- catch
448
-
449
- {
450
-
451
- throw new GetDataException(FileOpenErrorLog.Instance); // GetDataExceptionをスロー
452
-
453
- }
454
-
455
-
456
-
457
- var list = new List<string>();
458
-
459
- //...
460
-
461
- return list;
462
-
463
- }
464
-
465
- finally
466
-
467
- {
468
-
469
- reader?.Dispose();
470
-
471
- networkConnection?.Dispose();
472
-
473
- }
474
-
475
- }
476
-
477
- }
478
-
479
-
480
-
481
- #endregion
482
-
483
-
484
-
485
- #region IParseDataの実体
486
-
487
- // コレクションのデータを変換するIParseDataの実装
488
-
489
- class CollectionParser<TRawItem, TMappedItem> : IParseData<IEnumerable<TRawItem>, IEnumerable<TMappedItem>>
490
-
491
- {
492
-
493
- //...
494
-
495
-
496
-
497
- public CollectionParser(
498
-
499
- IValidateData<TRawItem> validator,
500
-
501
- IMapData<TRawItem, TMappedItem> mapper,
502
-
503
- IFixedLogger logger)
504
-
505
- {
506
-
507
- this._validator = validator;
508
-
509
- this._mapper = mapper;
510
-
511
- this._logger = logger;
512
-
513
- }
514
-
515
-
516
-
517
- public Tuple<bool, IEnumerable<TMappedItem>> ParseData(IEnumerable<TRawItem> rawItems)
518
-
519
- {
520
-
521
- // 変換の成否に関する戻り値設定に使う
522
-
523
- var inputItemsCount = 0;
524
-
525
-
526
-
527
- // 検証と変換に成功したもののみで絞込
528
-
529
- var parseResult = rawItems.Select(
530
-
531
- rawItem =>
532
-
533
- {
534
-
535
- // 生データコレクションカウント
536
-
537
- inputItemsCount++;
538
-
539
-
540
-
541
- if (_validator.ValidateData(rawItem)) // 検証してみる
542
-
543
- {
544
-
545
- // 検証OKならマッピングしてみる
546
-
547
- try
548
-
549
- {
550
-
551
- var mapped = this._mapper.MapData(rawItem);
552
-
553
- return new { Data = mapped, Result = true };
554
-
555
- }
556
-
557
- catch (MapException ex)
558
-
559
- {
560
-
561
- _logger.Log(ex.Log); // 失敗したらエラーログ
562
-
563
- }
564
-
565
- }
566
-
567
- // 検証失敗やマッピングで例外の場合は失敗を返す
568
-
569
- return new { Data = default(TMappedItem), Result = false };
570
-
571
- }).Where(mappingResult => mappingResult.Result).Select(mappingResult => mappingResult.Data).ToList();
572
-
573
-
574
-
575
- // 得られたコレクションの長さで判定
576
-
577
- return new Tuple<bool, IEnumerable<TMappedItem>>(parseResult.Count == inputItemsCount, parseResult);
578
-
579
- }
580
-
581
- }
582
-
583
-
584
-
585
- // 特定のソフトが出力するログを検証し、エラーログも出力するIValidateの実装
586
-
587
- class XXXXLogDataValidatorWithLog : IValidateData<string>
588
-
589
- {
590
-
591
- private readonly IFixedLogger _logger;
592
-
593
-
594
-
595
- public XXXXLogDataValidatorWithLog(IFixedLogger logger)
596
-
597
- {
598
-
599
- this._logger = logger;
600
-
601
- }
602
-
603
-
604
-
605
- public bool ValidateData(string data)
606
-
607
- {
608
-
609
- var result = data == string.Empty;
610
-
611
- // 失敗したらログを出力
612
-
613
- if (!result)
614
-
615
- {
616
-
617
- _logger.Log(UnavailableDataDetectedErrorLog.GetInstance(data, "文字列が空文字"));
618
-
619
- }
620
-
621
-
622
-
623
- return result;
624
-
625
- }
626
-
627
- }
628
-
629
-
630
-
631
- // 特定のソフトが出力するログをオブジェクトに変換するIMapの実装
632
-
633
- class XXXSimpleMapper : IMapData<string, Something>
634
-
635
- {
636
-
637
- public Something MapData(string raw)
638
-
639
- {
640
-
641
- string name;
642
-
643
- string message;
644
-
645
-
646
-
647
- try
648
-
649
- {
650
-
651
- name = raw.Substring(0, 10).TrimStart();
652
-
653
- }
654
-
655
- catch
656
-
657
- {
658
-
659
- // ここや
660
-
661
- throw new SimpleMapExeption("名前が変換できない", raw);
662
-
663
- }
664
-
665
-
666
-
667
- try
668
-
669
- {
670
-
671
- message = raw.Substring(10);
672
-
673
- }
674
-
675
- catch
676
-
677
- {
678
-
679
- // ここは、MapExceptionではなく、予期しないエラーになるべきか?
680
-
681
- // あるいは、ここでログを出力する?
682
-
683
- // その場合返すものがなくなる
684
-
685
- // IParseDataにかえて、ここでログをはきfalseを返す?
686
-
687
- // そもそもIMapいる?
688
-
689
- throw new SimpleMapExeption("メッセージが変換できない", raw);
690
-
691
- }
692
-
693
-
694
-
695
- return new Something(name, message);
696
-
697
- }
698
-
699
- }
700
-
701
-
702
-
703
- #endregion
704
-
705
-
706
-
707
- #region ISendDataの実体
708
-
709
- // データをファイルに送信するISendDataの実装
710
-
711
- class PlainFileSender : ISendData<IEnumerable<Something>>, IDisposable
712
-
713
- {
714
-
715
- private readonly StreamWriter _writer;
716
-
717
-
718
-
719
- public PlainFileSender(string path)
720
-
721
- {
722
-
723
- this._writer = new StreamWriter(path, true);
724
-
725
- }
726
-
727
- public void SendData(IEnumerable<Something> data)
728
-
729
- {
730
-
731
- try
732
-
733
- {
734
-
735
- // データをテキストファイルに追加
736
-
737
- }
738
-
739
- catch
740
-
741
- {
742
-
743
- var log = FileAppendErrorLog.Instance;
744
-
745
- throw new SendDataException(FileAppendErrorLog.Instance);
746
-
747
- }
748
-
749
- }
750
-
751
-
752
-
753
- public void Dispose()
754
-
755
- {
756
-
757
- this._writer?.Dispose();
758
-
759
- }
760
-
761
- }
762
-
763
-
764
-
765
- #endregion
766
-
767
687
  ```質問は以下になります。
768
688
 
769
689
  0. MapDataで発生した例外は予期しないエラーとするべきか意見をください。
770
690
 
771
- 0. 出力対象がファイルに変更され、ファイルは開始時にオープンして終了時にクローズすることになりました。そのためにPlainFileSenderクラスをISendData実装クラスとして作成したのですが、コンストラクタで例外が起きると現在のコードだと予期しないエラーになってしまいます。しかし、FatalErrorを投げていいのは処理の流れについて管理するやつだけです。
772
-
773
- 処理を継続できないエラーをGetParseSendFlowが判断してFatalErrorをスローするのと同じようにこのパターンでログを出力して異常終了できるようになる方法について意見をください。
774
-
775
- GetParseSendFlowと同じようなレベルのクラスをもう一つ作り、エントリポイントで先にそっちを実行して戻り値でStreamWriterを受け取ってPlainFileSenderに渡すような方法は思いつきました。(何一つスマートではないですが)
776
-
777
691
  よろしくお願いします。

5

追記

2017/01/26 16:05

投稿

syogakusya
syogakusya

スコア67

test CHANGED
File without changes
test CHANGED
@@ -1,6 +1,6 @@
1
1
  #質問
2
2
 
3
- 以下のようなコードについて考えています。
3
+ 以下が改善中最新のコードす。
4
4
 
5
5
  ```C#
6
6
 
@@ -12,6 +12,18 @@
12
12
 
13
13
  interface IGetData<out T>
14
14
 
15
+ Tuple<bool, TParsed> ParseData(TRaw data);
16
+
17
+ }
18
+
19
+
20
+
21
+ // データを検証
22
+
23
+ interface IValidateData<in T>
24
+
25
+
26
+
15
27
  {
16
28
 
17
29
  // 必ずGetできる。失敗するのは例外的状況
@@ -30,20 +42,6 @@
30
42
 
31
43
  // 与えるrawによって失敗。成功した場合Dataに結果が入る。
32
44
 
33
- Tuple<bool, TParsed> ParseData(TRaw data);
34
-
35
- }
36
-
37
-
38
-
39
- // データを検証
40
-
41
- interface IValidateData<in T>
42
-
43
- {
44
-
45
- // 与えるrawによって失敗。例外を投げない。
46
-
47
45
  bool ValidateData(T data);
48
46
 
49
47
  }
@@ -80,11 +78,35 @@
80
78
 
81
79
 
82
80
 
81
+ #endregion
82
+
83
+
84
+
85
+ #region 例外
86
+
87
+
88
+
83
- // ログイフェー
89
+ // アプリケーショ例外のベースとなる抽象例外クラス
84
-
90
+
85
- public interface IFixedLog
91
+ public abstract class MyNantokaApplicationException : Exception
86
-
92
+
87
- {
93
+ {
94
+
95
+ public IFixedLog Log { get; }
96
+
97
+
98
+
99
+ protected MyNantokaApplicationException(IFixedLog log)
100
+
101
+ : base()
102
+
103
+ {
104
+
105
+ this.Log = log;
106
+
107
+ }
108
+
109
+
88
110
 
89
111
  //...
90
112
 
@@ -92,13 +114,79 @@
92
114
 
93
115
 
94
116
 
95
- // ログ出力イフェース
117
+ // アプリケーショを強制終了するほどの致命的なエラを表す
96
-
118
+
97
- public interface IFixedLogger
119
+ public class FatalErrorException : MyNantokaApplicationException
98
-
120
+
99
- {
121
+ {
122
+
100
-
123
+ public FatalErrorException(IFixedLog log)
124
+
125
+ : base(log)
126
+
127
+ {
128
+
129
+ }
130
+
131
+
132
+
133
+ //...
134
+
135
+ }
136
+
137
+
138
+
139
+ // GetDataがGetに失敗したときに出す例外とする
140
+
141
+ public class GetDataException : MyNantokaApplicationException
142
+
143
+ {
144
+
145
+ public GetDataException(IFixedLog log)
146
+
147
+ : base(log)
148
+
149
+ {
150
+
151
+ }
152
+
153
+ }
154
+
155
+
156
+
157
+ // SendDataがSendに失敗したときに出す例外とする
158
+
159
+ public class SendDataException : MyNantokaApplicationException
160
+
161
+ {
162
+
163
+ public SendDataException(IFixedLog log)
164
+
165
+ : base(log)
166
+
167
+ {
168
+
169
+ }
170
+
171
+ }
172
+
173
+
174
+
175
+ // MapDataがMapに失敗したときに出す例外とする
176
+
177
+ // 失敗したら予期しないエラーのほうがいい?
178
+
179
+ public class MapException : MyNantokaApplicationException
180
+
181
+ {
182
+
101
- void Log(IFixedLog log);
183
+ public MapException(IFixedLog log)
184
+
185
+ : base(log)
186
+
187
+ {
188
+
189
+ }
102
190
 
103
191
  }
104
192
 
@@ -108,103 +196,281 @@
108
196
 
109
197
 
110
198
 
111
- #region 例外
199
+ #region エントリポイント
112
-
113
-
114
-
200
+
201
+
202
+
115
- // アプリケーション例外のベースとなる抽象例外クラス
203
+ class Program
116
-
117
- public abstract class MyNantokaApplicationException : Exception
204
+
118
-
119
- {
205
+ {
120
-
121
- public IFixedLog Log { get; }
206
+
122
-
123
-
124
-
125
- protected MyNantokaApplicationException(IFixedLog log)
126
-
127
- : base()
207
+ static int Main()
128
-
208
+
129
- {
209
+ {
210
+
130
-
211
+ var logger = new FixedLoggerToFile();
212
+
213
+ try
214
+
215
+ {
216
+
217
+ // このコンストラクタは例外的状況下で失敗するが、FatalErrorをなげないので予期しない例外になる!
218
+
219
+ using (var sender = new PlainFileSender(@"C:\...")) // Send)
220
+
221
+ {
222
+
223
+ var getter = new FileDataGetter("..."); // Get
224
+
225
+
226
+
227
+ var validator = new XXXXLogDataValidatorWithLog(logger);
228
+
229
+ var mapper = new XXXSimpleMapper();
230
+
231
+ var parser = new CollectionParser<string, Something>(validator, mapper, logger); // Parse
232
+
233
+
234
+
235
+ var test = new GetParseSendFlow<IEnumerable<string>, IEnumerable<Something>>(getter, parser, sender); // 合体
236
+
131
- this.Log = log;
237
+ test.Do(); // 処理実行
132
-
238
+
133
- }
239
+ }
240
+
241
+
242
+
134
-
243
+ return 0; // 正常終了
244
+
135
-
245
+ }
246
+
247
+ catch (FatalErrorException exception) // 致命的なエラーによる強制終了の場合は
248
+
249
+ {
250
+
251
+ var log = exception.Log; // 例外からLogを取り出す
252
+
253
+ if (log != null)
254
+
255
+ {
256
+
257
+ logger.Log(log); // ログを出力
258
+
259
+ }
260
+
261
+
262
+
263
+ return -1;
264
+
265
+ }
266
+
267
+ catch // 予期しない例外の場合は
268
+
269
+ {
270
+
271
+ var log = UnexpectedErrorLog.Instance;
272
+
273
+ logger.Log(log);
274
+
275
+ return -1;
276
+
277
+ }
278
+
279
+ }
280
+
281
+ }
282
+
283
+ #endregion
284
+
285
+
286
+
287
+ #region 元Testクラス
288
+
289
+ class GetParseSendFlow<TData, TParsed>
290
+
291
+ {
136
292
 
137
293
  //...
138
294
 
295
+
296
+
297
+ public GetParseSendFlow(IGetData<TData> getter, IParseData<TData, TParsed> parser, ISendData<TParsed> sender)
298
+
299
+ {
300
+
301
+ _getter = getter;
302
+
303
+ _parser = parser;
304
+
305
+ _sender = sender;
306
+
139
- }
307
+ }
140
-
141
-
142
-
143
- // アプリケーションを強制終了するほどの致命的なエラーを表す
308
+
144
-
309
+
310
+
145
- public class FatalErrorException : MyNantokaApplicationException
311
+ public void Do()
146
-
312
+
147
- {
313
+ {
148
-
149
- public FatalErrorException(IFixedLog log)
314
+
150
-
151
- : base(log)
315
+ var raw = default(TData);
316
+
317
+
318
+
152
-
319
+ try
320
+
153
- {
321
+ {
322
+
154
-
323
+ raw = _getter.GetData();
324
+
155
- }
325
+ }
326
+
156
-
327
+ catch (GetDataException exception)
328
+
157
-
329
+ {
330
+
331
+ var log = exception.Log; // GetDataExceptionから実際に失敗した理由のLogを取り出す
332
+
333
+ throw new FatalErrorException(log); // Get失敗は致命的なエラー扱いとして、そのログを入れてスロー
334
+
335
+ }
336
+
337
+
338
+
339
+ var parseResult = _parser.ParseData(raw); // ParseDataは例外を出さない
340
+
341
+
342
+
343
+ if (!parseResult.Item1)// parseが失敗した場合は
344
+
345
+ {
346
+
347
+ //var log = ParseFailedAndInterruptErrorLog.Instance; // 終了理由を入れてスロー
348
+
349
+ throw new FatalErrorException(null); // 要件を満たすためにとりあえずnull
350
+
351
+ }
352
+
353
+
354
+
355
+ try
356
+
357
+ {
358
+
359
+ _sender.SendData(parseResult.Item2);
360
+
361
+ }
362
+
363
+ catch (SendDataException exception) //
364
+
365
+ {
366
+
367
+ var log = exception.Log;
368
+
369
+ throw new FatalErrorException(log); // Send失敗は致命的なエラー扱いとして、そのログを入れてスロー
370
+
371
+ }
372
+
373
+ }
374
+
375
+ }
376
+
377
+
378
+
379
+ #endregion
380
+
381
+
382
+
383
+ #region IGetDataの実体
384
+
385
+ // ファイルから文字列のコレクションを取得するIGetDataの実装
386
+
387
+ public class FileDataGetter : IGetData<IEnumerable<string>>
388
+
389
+ {
158
390
 
159
391
  //...
160
392
 
161
- }
162
-
163
-
164
-
165
- // GetDataがGetに失敗したときに出す例外とする
166
-
167
- public abstract class GetDataException : MyNantokaApplicationException
168
-
169
- {
170
-
171
- public GetDataException(IFixedLog log)
172
-
173
- : base(log)
174
-
175
- {
176
-
177
- }
178
-
179
- }
180
-
181
-
182
-
183
- public abstract class SendDataException : MyNantokaApplicationException
184
-
185
- {
186
-
187
- public SendDataException(IFixedLog log)
188
-
189
- : base(log)
190
-
191
- {
192
-
193
- }
194
-
195
- }
196
-
197
-
198
-
199
- public abstract class MapException : MyNantokaApplicationException
200
-
201
- {
202
-
203
- public MapException(IFixedLog log)
204
-
205
- : base(log)
206
-
207
- {
393
+
394
+
395
+ public FileDataGetter(string path /** , string ... **/)
396
+
397
+ {
398
+
399
+ _path = path;
400
+
401
+ }
402
+
403
+
404
+
405
+ public IEnumerable<string> GetData()
406
+
407
+ {
408
+
409
+ StreamReader reader = null;
410
+
411
+ NetworkConnection networkConnection = null;
412
+
413
+
414
+
415
+ try
416
+
417
+ {
418
+
419
+ try
420
+
421
+ {
422
+
423
+ // ネットワークに接続
424
+
425
+ }
426
+
427
+ catch
428
+
429
+ {
430
+
431
+ throw new GetDataException(ConnectToNetworkForFileErrorLog.Instance); // GetDataExceptionをスロー
432
+
433
+ }
434
+
435
+
436
+
437
+ try
438
+
439
+ {
440
+
441
+ // ファイルオープン
442
+
443
+ reader = new StreamReader(_path);
444
+
445
+ }
446
+
447
+ catch
448
+
449
+ {
450
+
451
+ throw new GetDataException(FileOpenErrorLog.Instance); // GetDataExceptionをスロー
452
+
453
+ }
454
+
455
+
456
+
457
+ var list = new List<string>();
458
+
459
+ //...
460
+
461
+ return list;
462
+
463
+ }
464
+
465
+ finally
466
+
467
+ {
468
+
469
+ reader?.Dispose();
470
+
471
+ networkConnection?.Dispose();
472
+
473
+ }
208
474
 
209
475
  }
210
476
 
@@ -216,75 +482,217 @@
216
482
 
217
483
 
218
484
 
219
- #region エントリポイント
485
+ #region IParseDataの実体
220
-
221
-
222
-
486
+
223
- class Program
487
+ // コレクションのデータを変換するIParseDataの実装
488
+
224
-
489
+ class CollectionParser<TRawItem, TMappedItem> : IParseData<IEnumerable<TRawItem>, IEnumerable<TMappedItem>>
490
+
225
- {
491
+ {
492
+
226
-
493
+ //...
494
+
495
+
496
+
227
- static int Main()
497
+ public CollectionParser(
498
+
228
-
499
+ IValidateData<TRawItem> validator,
500
+
501
+ IMapData<TRawItem, TMappedItem> mapper,
502
+
503
+ IFixedLogger logger)
504
+
229
- {
505
+ {
506
+
230
-
507
+ this._validator = validator;
508
+
509
+ this._mapper = mapper;
510
+
511
+ this._logger = logger;
512
+
513
+ }
514
+
515
+
516
+
517
+ public Tuple<bool, IEnumerable<TMappedItem>> ParseData(IEnumerable<TRawItem> rawItems)
518
+
519
+ {
520
+
521
+ // 変換の成否に関する戻り値設定に使う
522
+
523
+ var inputItemsCount = 0;
524
+
525
+
526
+
527
+ // 検証と変換に成功したもののみで絞込
528
+
529
+ var parseResult = rawItems.Select(
530
+
531
+ rawItem =>
532
+
533
+ {
534
+
535
+ // 生データコレクションカウント
536
+
537
+ inputItemsCount++;
538
+
539
+
540
+
541
+ if (_validator.ValidateData(rawItem)) // 検証してみる
542
+
543
+ {
544
+
545
+ // 検証OKならマッピングしてみる
546
+
547
+ try
548
+
549
+ {
550
+
551
+ var mapped = this._mapper.MapData(rawItem);
552
+
553
+ return new { Data = mapped, Result = true };
554
+
555
+ }
556
+
557
+ catch (MapException ex)
558
+
559
+ {
560
+
561
+ _logger.Log(ex.Log); // 失敗したらエラーログ
562
+
563
+ }
564
+
565
+ }
566
+
567
+ // 検証失敗やマッピングで例外の場合は失敗を返す
568
+
569
+ return new { Data = default(TMappedItem), Result = false };
570
+
571
+ }).Where(mappingResult => mappingResult.Result).Select(mappingResult => mappingResult.Data).ToList();
572
+
573
+
574
+
575
+ // 得られたコレクションの長さで判定
576
+
577
+ return new Tuple<bool, IEnumerable<TMappedItem>>(parseResult.Count == inputItemsCount, parseResult);
578
+
579
+ }
580
+
581
+ }
582
+
583
+
584
+
585
+ // 特定のソフトが出力するログを検証し、エラーログも出力するIValidateの実装
586
+
587
+ class XXXXLogDataValidatorWithLog : IValidateData<string>
588
+
589
+ {
590
+
231
- var logger = new FixedLoggerToFile();
591
+ private readonly IFixedLogger _logger;
592
+
593
+
594
+
595
+ public XXXXLogDataValidatorWithLog(IFixedLogger logger)
596
+
597
+ {
598
+
599
+ this._logger = logger;
600
+
601
+ }
602
+
603
+
604
+
605
+ public bool ValidateData(string data)
606
+
607
+ {
608
+
609
+ var result = data == string.Empty;
610
+
611
+ // 失敗したらログを出力
612
+
613
+ if (!result)
614
+
615
+ {
616
+
617
+ _logger.Log(UnavailableDataDetectedErrorLog.GetInstance(data, "文字列が空文字"));
618
+
619
+ }
620
+
621
+
622
+
623
+ return result;
624
+
625
+ }
626
+
627
+ }
628
+
629
+
630
+
631
+ // 特定のソフトが出力するログをオブジェクトに変換するIMapの実装
632
+
633
+ class XXXSimpleMapper : IMapData<string, Something>
634
+
635
+ {
636
+
637
+ public Something MapData(string raw)
638
+
639
+ {
640
+
641
+ string name;
642
+
643
+ string message;
644
+
645
+
232
646
 
233
647
  try
234
648
 
235
649
  {
236
650
 
237
- var getter = new FileDataGetter("..."); // Get
238
-
239
-
240
-
241
- var validator = new XXXXLogDataValidatorWithLog(logger);
242
-
243
- var mapper = new XXXSimpleMapper();
651
+ name = raw.Substring(0, 10).TrimStart();
244
-
245
- var parser = new CollectionParser<string, Something>(validator, mapper, logger); // Parse
652
+
246
-
247
-
248
-
249
- var sender = new SenderToMyDatabase(); // Send
250
-
251
-
252
-
253
- var test = new GetParseSendFlow<IEnumerable<string>, IEnumerable<Something>>(getter, parser, sender); // 合体
254
-
255
- test.Do(); // 処理
256
-
257
-
258
-
259
- return 0; // 正常終了
260
-
261
- }
653
+ }
262
-
654
+
263
- catch (FatalErrorException exception) // 致命的なエラーによる強制終了の場合は
655
+ catch
264
-
656
+
265
- {
657
+ {
266
-
267
- var log = exception.Log; // 例外からLogを取り出す
658
+
268
-
269
- if (log != null)
270
-
271
- logger.Log(log); // ログを出力
659
+ // ここや
272
-
660
+
273
- return -1;
661
+ throw new SimpleMapExeption("名前が変換できない", raw);
274
-
662
+
275
- }
663
+ }
276
-
664
+
665
+
666
+
277
- catch // 予期しない例外の場合は
667
+ try
278
-
668
+
279
- {
669
+ {
280
-
281
- var log = UnexpectedErrorLog.Instance;
670
+
282
-
283
- logger.Log(log);
671
+ message = raw.Substring(10);
284
-
285
- return -1;
672
+
286
-
287
- }
673
+ }
674
+
675
+ catch
676
+
677
+ {
678
+
679
+ // ここは、MapExceptionではなく、予期しないエラーになるべきか?
680
+
681
+ // あるいは、ここでログを出力する?
682
+
683
+ // その場合返すものがなくなる
684
+
685
+ // IParseDataにかえて、ここでログをはきfalseを返す?
686
+
687
+ // そもそもIMapいる?
688
+
689
+ throw new SimpleMapExeption("メッセージが変換できない", raw);
690
+
691
+ }
692
+
693
+
694
+
695
+ return new Something(name, message);
288
696
 
289
697
  }
290
698
 
@@ -296,229 +704,57 @@
296
704
 
297
705
 
298
706
 
299
- #region 元Testクラス
707
+ #region ISendDataの実体
708
+
300
-
709
+ // データをファイルに送信するISendDataの実装
710
+
301
- class GetParseSendFlow<TData, TParsed>
711
+ class PlainFileSender : ISendData<IEnumerable<Something>>, IDisposable
302
-
712
+
303
- {
713
+ {
304
-
305
- //...
714
+
306
-
307
-
308
-
309
- public GetParseSendFlow(IGetData<TData> getter, IParseData<TData, TParsed> parser, ISendData<TParsed> sender)
715
+ private readonly StreamWriter _writer;
716
+
717
+
718
+
310
-
719
+ public PlainFileSender(string path)
720
+
311
- {
721
+ {
312
-
722
+
313
- _getter = getter;
723
+ this._writer = new StreamWriter(path, true);
314
-
315
- _parser = parser;
724
+
316
-
317
- _sender = sender;
318
-
319
- }
725
+ }
320
-
321
-
322
-
726
+
323
- public void Do()
727
+ public void SendData(IEnumerable<Something> data)
324
-
728
+
325
- {
729
+ {
326
-
327
- var raw = default(TData);
328
-
329
-
330
730
 
331
731
  try
332
732
 
333
733
  {
334
734
 
335
- raw = _getter.GetData();
336
-
337
- }
338
-
339
- catch (GetDataException exception)
340
-
341
- {
342
-
343
- var log = exception.Log; // GetDataExceptionから実際に失敗した理由のLogを取り出す
344
-
345
- throw new FatalErrorException(log); // Get失敗は致命的なエラー扱いとして、そのログを入れてスロー
346
-
347
- }
348
-
349
-
350
-
351
- var parseResult = _parser.ParseData(raw); // ParseDataは例外を出さない
352
-
353
-
354
-
355
- if (!parseResult.Item1)// parseが失敗した場合は
356
-
357
- {
358
-
359
- //var log = ParseFailedAndInterruptErrorLog.Instance; // 終了理由を入れてスロー
360
-
361
- throw new FatalErrorException(null); // 要件を満たすためにとりあえずnull
362
-
363
- }
364
-
365
-
366
-
367
- try
368
-
369
- {
370
-
371
- _sender.SendData(parseResult.Item2);
372
-
373
- }
374
-
375
- catch (SendDataException exception) //
376
-
377
- {
378
-
379
- var log = exception.Log;
380
-
381
- throw new FatalErrorException(log); // Send失敗は致命的なエラー扱いとして、そのログを入れてスロー
382
-
383
- }
384
-
385
- }
386
-
387
- }
388
-
389
-
390
-
391
- #endregion
392
-
393
-
394
-
395
- #region IGetDataの実体
396
-
397
- // ファイルから文字列のコレクションを取得するIGetDataの実装
398
-
399
- public class FileDataGetter : IGetData<IEnumerable<string>>
400
-
401
- {
402
-
403
- //...
404
-
405
-
406
-
407
- public FileDataGetter(string path /** , string ... **/)
408
-
409
- {
410
-
411
- _path = path;
412
-
413
- }
414
-
415
-
416
-
417
- public IEnumerable<string> GetData()
418
-
419
- {
420
-
421
- StreamReader reader = null;
422
-
423
- NetworkConnection networkConnection = null;
424
-
425
-
426
-
427
- try
428
-
429
- {
430
-
431
- try
432
-
433
- {
434
-
435
- // ネットワークに接続
436
-
437
- }
438
-
439
- catch
440
-
441
- {
442
-
443
- throw new NetworkConnectionException(); // GetDataExceptionをスロー
444
-
445
- }
446
-
447
-
448
-
449
- try
450
-
451
- {
452
-
453
- // ファイルオープン
454
-
455
- reader = new StreamReader(_path);
456
-
457
- }
458
-
459
- catch
460
-
461
- {
462
-
463
- throw new FileOpenException(); // GetDataExceptionをスロー
464
-
465
- }
466
-
467
-
468
-
469
- var list = new List<string>();
470
-
471
- //...
472
-
473
- return list;
474
-
475
- }
476
-
477
- finally
478
-
479
- {
480
-
481
- reader?.Dispose();
482
-
483
- networkConnection?.Dispose();
484
-
485
- }
486
-
487
- }
488
-
489
- }
490
-
491
-
492
-
493
- // ファイルのオープンに失敗したときのエラーを表す
494
-
495
- // GetDataExceptionを投げる?
496
-
497
- public class FileOpenException : GetDataException
498
-
499
- {
500
-
501
- public FileOpenException()
502
-
503
- : base(FileOpenErrorLog.Instance)
504
-
505
- {
506
-
507
- }
508
-
509
- }
510
-
511
- // 同上
512
-
513
- public class NetworkConnectionException : GetDataException
514
-
515
- {
516
-
517
- public NetworkConnectionException()
518
-
519
- : base(ConnectToNetworkForFileErrorLog.Instance)
520
-
521
- {
735
+ // データをテキストファイルに追加
736
+
737
+ }
738
+
739
+ catch
740
+
741
+ {
742
+
743
+ var log = FileAppendErrorLog.Instance;
744
+
745
+ throw new SendDataException(FileAppendErrorLog.Instance);
746
+
747
+ }
748
+
749
+ }
750
+
751
+
752
+
753
+ public void Dispose()
754
+
755
+ {
756
+
757
+ this._writer?.Dispose();
522
758
 
523
759
  }
524
760
 
@@ -528,294 +764,14 @@
528
764
 
529
765
  #endregion
530
766
 
531
-
532
-
533
- #region IParseDataの実体
534
-
535
- // コレションのデタを変換するIParseData実装
536
-
537
- class CollectionParser<TRawItem, TMappedItem> : IParseData<IEnumerable<TRawItem>, IEnumerable<TMappedItem>>
538
-
539
- {
540
-
541
- //...
542
-
543
-
544
-
545
- public CollectionParser(
546
-
547
- IValidateData<TRawItem> validator,
548
-
549
- IMapData<TRawItem, TMappedItem> mapper,
550
-
551
- IFixedLogger logger)
552
-
553
- {
554
-
555
- this._validator = validator;
556
-
557
- this._mapper = mapper;
558
-
559
- this._logger = logger;
560
-
561
- }
562
-
563
-
564
-
565
- public Tuple<bool, IEnumerable<TMappedItem>> ParseData(IEnumerable<TRawItem> rawItems)
566
-
567
- {
568
-
569
- // 変換の成否に関する戻り値設定に使う
570
-
571
- var inputItemsCount = 0;
572
-
573
-
574
-
575
- // 検証と変換に成功したもののみで絞込
576
-
577
- var parseResult = rawItems.Select(
578
-
579
- rawItem =>
580
-
581
- {
582
-
583
- // 生データコレクションカウント
584
-
585
- inputItemsCount++;
586
-
587
-
588
-
589
- if (_validator.ValidateData(rawItem)) // 検証してみる
590
-
591
- {
592
-
593
- // 検証OKならマッピングしてみる
594
-
595
- try
596
-
597
- {
598
-
599
- var mapped = this._mapper.MapData(rawItem);
600
-
601
- return new { Data = mapped, Result = true };
602
-
603
- }
604
-
605
- catch (MapException ex)
606
-
607
- {
608
-
609
- _logger.Log(ex.Log); // 失敗したらエラーログ
610
-
611
- }
612
-
613
- }
614
-
615
- // 検証失敗やマッピングで例外の場合は失敗を返す
616
-
617
- return new { Data = default(TMappedItem), Result = false };
618
-
619
- }).Where(mappingResult => mappingResult.Result).Select(mappingResult => mappingResult.Data).ToList();
620
-
621
-
622
-
623
- // 得られたコレクションの長さで判定
624
-
625
- return new Tuple<bool, IEnumerable<TMappedItem>>(parseResult.Count == inputItemsCount, parseResult);
626
-
627
- }
628
-
629
- }
630
-
631
-
632
-
633
- // 特定のソフトが出力するログを検証し、エラーログも出力するIValidateの実装
634
-
635
- class XXXXLogDataValidatorWithLog : IValidateData<string>
636
-
637
- {
638
-
639
- private readonly IFixedLogger _logger;
640
-
641
-
642
-
643
- public XXXXLogDataValidatorWithLog(IFixedLogger logger)
644
-
645
- {
646
-
647
- this._logger = logger;
648
-
649
- }
650
-
651
-
652
-
653
- public bool ValidateData(string data)
654
-
655
- {
656
-
657
- var result = data == string.Empty;
658
-
659
- // 失敗したらログを出力
660
-
661
- if (!result)
662
-
663
- {
664
-
665
- _logger.Log(UnavailableDataDetectedErrorLog.GetInstance(data, "文字列が空文字"));
666
-
667
- }
668
-
669
-
670
-
671
- return result;
672
-
673
- }
674
-
675
- }
676
-
677
-
678
-
679
- // 特定のソフトが出力するログをオブジェクトに変換するIMapの実装
680
-
681
- class XXXSimpleMapper : IMapData<string, Something>
682
-
683
- {
684
-
685
- public Something MapData(string raw)
686
-
687
- {
688
-
689
- string name;
690
-
691
- string message;
692
-
693
-
694
-
695
- try
696
-
697
- {
698
-
699
- name = raw.Substring(0, 10).TrimStart();
700
-
701
- }
702
-
703
- catch
704
-
705
- {
706
-
707
- // ここや
708
-
709
- throw new SimpleMapExeption("名前が変換できない", raw);
710
-
711
- }
712
-
713
-
714
-
715
- try
716
-
717
- {
718
-
719
- message = raw.Substring(10);
720
-
721
- }
722
-
723
- catch
724
-
725
- {
726
-
727
- // ここは、MapExceptionではなく、予期しないエラーになるべきか?
728
-
729
- // 設計的にValidateでtrueが返されてる時点でここでの失敗は予期されない?
730
-
731
- // その場合、Validateを頑張って作らないといけなくなる
732
-
733
- // あるいは、ここでログを出力する?
734
-
735
- // その場合返すものがなくなる
736
-
737
- // IParseDataにかえて、ここでログをはきfalseを返す?
738
-
739
- // そもそもIMapいる?
740
-
741
- throw new SimpleMapExeption("メッセージが変換できない", raw);
742
-
743
- }
744
-
745
-
746
-
747
- return new Something(name, message);
748
-
749
- }
750
-
751
- }
752
-
753
-
754
-
755
- // IMapにMapExceptionは必要?
756
-
757
- public class SimpleMapExeption : MapException
758
-
759
- {
760
-
761
- public SimpleMapExeption(string errorReason, string errorData)
762
-
763
- : base(UnavailableDataDetectedErrorLog.GetInstance(errorReason, errorData))
764
-
765
- {
766
-
767
- }
768
-
769
- }
770
-
771
-
772
-
773
- #endregion
774
-
775
-
776
-
777
- #region ISendDataの実体
778
-
779
- // DBに送信するISendDataの実装
780
-
781
- class SenderToMyDatabase : ISendData<IEnumerable<Something>>
782
-
783
- {
784
-
785
- public void SendData(IEnumerable<Something> data)
786
-
787
- {
788
-
789
- try
790
-
791
- {
792
-
793
- // データを保存
794
-
795
- }
796
-
797
- catch
798
-
799
- {
800
-
801
- // 送信失敗のLog入りでSendDataExceptionをスロー
802
-
803
- }
804
-
805
- }
806
-
807
- }
808
-
809
-
810
-
811
- #endregion
812
-
813
- ```コードをとりあえず載せておきます。
814
-
815
- 突っ込みどころがあれば指摘してください。
816
-
817
- コンパイル可能には、文字数の関係で無理でした。
818
-
819
- 後ほど追記します質問にご意見いただければ幸いです。
820
-
821
- よろしくお願いいたします。
767
+ ```質問は以下になります。
768
+
769
+ 0. MapDataで発生した例外は予期しないエラーとするべきか意見をください。
770
+
771
+ 0. 出力対象がファイルに変更され、ファイルは開始時にオープンして終了時にすることになりました。そのためにPlainFileSenderクラスをISendData実装クラスとして作成したのですが、コンストラクタで例外が起きると現在のコードだと予期しないエラーになってしまいます。しかし、FatalErrorを投げていいのは処理の流れについて管理するやつだけです。
772
+
773
+ 処理を継続できないエラーをGetParseSendFlowが判断してFatalErrorをスローするのと同じようにこのパターンでログを出力して異常終了できるようになる方法について意見をください。
774
+
775
+ GetParseSendFlowと同じようなレベルのクラスをもう一つ作り、エントリポイントで先にそっちを実行して戻り値でStreamWriterを受け取ってPlainFileSenderに渡すような方法は思いつきました。(何一つスマートではないですが)
776
+
777
+ よろしくお願いします。

4

追記の修正

2017/01/23 15:48

投稿

syogakusya
syogakusya

スコア67

test CHANGED
File without changes
test CHANGED
@@ -538,6 +538,10 @@
538
538
 
539
539
  {
540
540
 
541
+ //...
542
+
543
+
544
+
541
545
  public CollectionParser(
542
546
 
543
547
  IValidateData<TRawItem> validator,

3

追記

2017/01/22 15:29

投稿

syogakusya
syogakusya

スコア67

test CHANGED
File without changes
test CHANGED
@@ -1,80 +1,320 @@
1
- ###質問
2
-
3
- データを取得し、変換し、送信するような処理があります。
4
-
5
- このとき、solid原則に基づいて以下のようなクラスを作成しました。
6
-
7
-
8
-
9
- ### ソースコード
10
-
11
- class Test
12
-
13
-
14
-
15
- public Test(IGetData getter, IParseData parser, ISendData sender)
16
-
17
-
18
-
19
- ...
20
-
21
-
22
-
23
-
24
-
25
- public Do()
26
-
27
-
28
-
29
- var raw = _getter.GetData();
30
-
31
- var parsed = _parser.ParseData(raw);
32
-
33
- _sender.Send(parsed);
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
- ###問題点
42
-
43
- このパターンで多くの問題を解決してきたのですが、今回のプロジェクトで少し悩んでいます。
44
-
45
- まず、現段階でGetDataの実装はファイルからの取得なのですが、このとき、ファイルがなければ正常終了しなければなりません。
46
-
47
- ParseDataも同様に、与えられたデータを解析して変更フラグが立っていなければ、正常終了します。
48
-
49
- この"分岐する正常終了"が悩みの一つです。インターフェースのシグネチャを変えたらいいのか、このクラスを変えたらいいのか…。こういったパターンは初めてのことで、プログラム設計についてかなり悩んでいます。(今回作成するアプリの仕様が厳密に決まっていることも要因の一つかと思います。)
50
-
51
- 正常終了する場合、正常終了のパターンに応じてログを出力したりします。
52
-
53
- 二つ目も同じような悩みです。つまり、オブジェクトツリーを組み立てて委譲により問題を解決する場合の、コードパスの問題です。
54
-
55
- 二つ目の悩みは、"分岐する異常終了"です。たとえば、ParseDataでデータの変換に失敗した場合、決められたエラーログを出力して異常終了します。このとき、実行ファイルとして-1を返さなければなりません。IParseDataをtry-catchして、決められたエラーログを出力して再スローするようなIParseDataのデコレーターを作ることを考えたのですが、果たして本当にそれでいいのかということを悩んでいます。エントリポイントで全体をtry-catchしてcatchに入ったら-1というような作りにしてもいいのでしょうか。
56
-
57
- その場合、異常終了するためには必ず例外をスローしなければなりませんが、例えばファイルがない場合に正常終了ではなく異常終了したくなった場合、ファイルがなければ普通に例外をスローしてエントリポイントで捕捉するようなことになると思います。
58
-
59
- よろしくお願いします。
60
-
61
-
62
-
63
- #追記2
64
-
65
- 皆様からいただきました意見を総合し、現在以下のようなコードについて考えています。
1
+ #質問
2
+
3
+ 以下のようなコードについて考えています。
66
4
 
67
5
  ```C#
68
6
 
69
- class TestClass<TData, TParsed>
70
-
71
- {
72
-
73
- public TestClass(IGetData<TData> getter, IParseData<TData, TParsed> parser, ISendData<TParsed> sender)
74
-
75
- {
76
-
77
- // ...
7
+ #region インターフェース
8
+
9
+
10
+
11
+ // データを取得
12
+
13
+ interface IGetData<out T>
14
+
15
+ {
16
+
17
+ // 必ずGetできる。失敗するのは例外的状況
18
+
19
+ T GetData();
20
+
21
+ }
22
+
23
+
24
+
25
+ // データを変換
26
+
27
+ interface IParseData<in TRaw, TParsed>
28
+
29
+ {
30
+
31
+ // 与えるrawによって失敗。成功した場合Dataに結果が入る。
32
+
33
+ Tuple<bool, TParsed> ParseData(TRaw data);
34
+
35
+ }
36
+
37
+
38
+
39
+ // データを検証
40
+
41
+ interface IValidateData<in T>
42
+
43
+ {
44
+
45
+ // 与えるrawによって失敗。例外を投げない。
46
+
47
+ bool ValidateData(T data);
48
+
49
+ }
50
+
51
+
52
+
53
+ // データをマッピング
54
+
55
+ // 必ずMapできる。失敗するのは例外的状況
56
+
57
+ // 失敗したら予期しないエラーにする?
58
+
59
+ interface IMapData<in TRaw, out TMapped>
60
+
61
+ {
62
+
63
+ TMapped MapData(TRaw raw);
64
+
65
+ }
66
+
67
+
68
+
69
+ // データを送信
70
+
71
+ interface ISendData<in T>
72
+
73
+ {
74
+
75
+ // 必ずSendできる。失敗するのは例外的状況
76
+
77
+ void SendData(T data);
78
+
79
+ }
80
+
81
+
82
+
83
+ // ログインターフェース
84
+
85
+ public interface IFixedLog
86
+
87
+ {
88
+
89
+ //...
90
+
91
+ }
92
+
93
+
94
+
95
+ // ログ出力インターフェース
96
+
97
+ public interface IFixedLogger
98
+
99
+ {
100
+
101
+ void Log(IFixedLog log);
102
+
103
+ }
104
+
105
+
106
+
107
+ #endregion
108
+
109
+
110
+
111
+ #region 例外
112
+
113
+
114
+
115
+ // アプリケーション例外のベースとなる抽象例外クラス
116
+
117
+ public abstract class MyNantokaApplicationException : Exception
118
+
119
+ {
120
+
121
+ public IFixedLog Log { get; }
122
+
123
+
124
+
125
+ protected MyNantokaApplicationException(IFixedLog log)
126
+
127
+ : base()
128
+
129
+ {
130
+
131
+ this.Log = log;
132
+
133
+ }
134
+
135
+
136
+
137
+ //...
138
+
139
+ }
140
+
141
+
142
+
143
+ // アプリケーションを強制終了するほどの致命的なエラーを表す
144
+
145
+ public class FatalErrorException : MyNantokaApplicationException
146
+
147
+ {
148
+
149
+ public FatalErrorException(IFixedLog log)
150
+
151
+ : base(log)
152
+
153
+ {
154
+
155
+ }
156
+
157
+
158
+
159
+ //...
160
+
161
+ }
162
+
163
+
164
+
165
+ // GetDataがGetに失敗したときに出す例外とする
166
+
167
+ public abstract class GetDataException : MyNantokaApplicationException
168
+
169
+ {
170
+
171
+ public GetDataException(IFixedLog log)
172
+
173
+ : base(log)
174
+
175
+ {
176
+
177
+ }
178
+
179
+ }
180
+
181
+
182
+
183
+ public abstract class SendDataException : MyNantokaApplicationException
184
+
185
+ {
186
+
187
+ public SendDataException(IFixedLog log)
188
+
189
+ : base(log)
190
+
191
+ {
192
+
193
+ }
194
+
195
+ }
196
+
197
+
198
+
199
+ public abstract class MapException : MyNantokaApplicationException
200
+
201
+ {
202
+
203
+ public MapException(IFixedLog log)
204
+
205
+ : base(log)
206
+
207
+ {
208
+
209
+ }
210
+
211
+ }
212
+
213
+
214
+
215
+ #endregion
216
+
217
+
218
+
219
+ #region エントリポイント
220
+
221
+
222
+
223
+ class Program
224
+
225
+ {
226
+
227
+ static int Main()
228
+
229
+ {
230
+
231
+ var logger = new FixedLoggerToFile();
232
+
233
+ try
234
+
235
+ {
236
+
237
+ var getter = new FileDataGetter("..."); // Get
238
+
239
+
240
+
241
+ var validator = new XXXXLogDataValidatorWithLog(logger);
242
+
243
+ var mapper = new XXXSimpleMapper();
244
+
245
+ var parser = new CollectionParser<string, Something>(validator, mapper, logger); // Parse
246
+
247
+
248
+
249
+ var sender = new SenderToMyDatabase(); // Send
250
+
251
+
252
+
253
+ var test = new GetParseSendFlow<IEnumerable<string>, IEnumerable<Something>>(getter, parser, sender); // 合体
254
+
255
+ test.Do(); // 処理
256
+
257
+
258
+
259
+ return 0; // 正常終了
260
+
261
+ }
262
+
263
+ catch (FatalErrorException exception) // 致命的なエラーによる強制終了の場合は
264
+
265
+ {
266
+
267
+ var log = exception.Log; // 例外からLogを取り出す
268
+
269
+ if (log != null)
270
+
271
+ logger.Log(log); // ログを出力
272
+
273
+ return -1;
274
+
275
+ }
276
+
277
+ catch // 予期しない例外の場合は
278
+
279
+ {
280
+
281
+ var log = UnexpectedErrorLog.Instance;
282
+
283
+ logger.Log(log);
284
+
285
+ return -1;
286
+
287
+ }
288
+
289
+ }
290
+
291
+ }
292
+
293
+
294
+
295
+ #endregion
296
+
297
+
298
+
299
+ #region 元Testクラス
300
+
301
+ class GetParseSendFlow<TData, TParsed>
302
+
303
+ {
304
+
305
+ //...
306
+
307
+
308
+
309
+ public GetParseSendFlow(IGetData<TData> getter, IParseData<TData, TParsed> parser, ISendData<TParsed> sender)
310
+
311
+ {
312
+
313
+ _getter = getter;
314
+
315
+ _parser = parser;
316
+
317
+ _sender = sender;
78
318
 
79
319
  }
80
320
 
@@ -86,54 +326,492 @@
86
326
 
87
327
  var raw = default(TData);
88
328
 
329
+
330
+
89
331
  try
90
332
 
91
333
  {
92
334
 
93
- raw = _getter.GetData(); // GetDataは基本成功
94
-
95
- }
96
-
97
- catch (GetDataException) // GetDataがGet失敗した時に出す例外「だけ」をcatch
98
-
99
- {
100
-
101
- var log = CreateLog("ファイルオープン失敗");
102
-
103
- throw new FatalErrorException(log); // 致命的なエラーを例外で表現する
104
-
105
- }
106
-
107
-
108
-
109
- var parsed _parser.ParseData(raw); // ParseDataは例外を出さない
110
-
111
-
112
-
113
- if (!parsed.Success) // parseが失敗した場合は
114
-
115
- {
116
-
117
- var log = CreateLog("デタが〇〇社のデータ変換ソフトウェア『××』では変換できません。");
118
-
119
- throw new FatalErrorException(log); // ログ入スロー
120
-
121
- }
122
-
123
-
124
-
125
- _sender.Send(parsed.Data);
126
-
127
- }
128
-
129
- }
130
-
131
-
132
-
133
- ```
134
-
135
- しかし、このコードですとDoメソッドがIGetDataやIParseDataの実装について知っていることになってしまいます。
136
-
137
- このコードをよくするための方針について悩んでいます。
138
-
139
- 引き続きよろしくお願いたます。
335
+ raw = _getter.GetData();
336
+
337
+ }
338
+
339
+ catch (GetDataException exception)
340
+
341
+ {
342
+
343
+ var log = exception.Log; // GetDataExceptionから実際に失敗した理由のLogを取り出す
344
+
345
+ throw new FatalErrorException(log); // Get失敗は致命的なエラー扱いとして、そのログ入れてスロー
346
+
347
+ }
348
+
349
+
350
+
351
+ var parseResult = _parser.ParseData(raw); // ParseDataは例外を出さない
352
+
353
+
354
+
355
+ if (!parseResult.Item1)// parseが失敗した場合は
356
+
357
+ {
358
+
359
+ //var log = ParseFailedAndInterruptErrorLog.Instance; // 終了理由を入れてスロ
360
+
361
+ throw new FatalErrorException(null); // 要件を満たすためにとあえずnull
362
+
363
+ }
364
+
365
+
366
+
367
+ try
368
+
369
+ {
370
+
371
+ _sender.SendData(parseResult.Item2);
372
+
373
+ }
374
+
375
+ catch (SendDataException exception) //
376
+
377
+ {
378
+
379
+ var log = exception.Log;
380
+
381
+ throw new FatalErrorException(log); // Send失敗は致命的なエラー扱て、そのログを入れてスロー
382
+
383
+ }
384
+
385
+ }
386
+
387
+ }
388
+
389
+
390
+
391
+ #endregion
392
+
393
+
394
+
395
+ #region IGetDataの実体
396
+
397
+ // ファイルから文字列のコレクションを取得するIGetDataの実装
398
+
399
+ public class FileDataGetter : IGetData<IEnumerable<string>>
400
+
401
+ {
402
+
403
+ //...
404
+
405
+
406
+
407
+ public FileDataGetter(string path /** , string ... **/)
408
+
409
+ {
410
+
411
+ _path = path;
412
+
413
+ }
414
+
415
+
416
+
417
+ public IEnumerable<string> GetData()
418
+
419
+ {
420
+
421
+ StreamReader reader = null;
422
+
423
+ NetworkConnection networkConnection = null;
424
+
425
+
426
+
427
+ try
428
+
429
+ {
430
+
431
+ try
432
+
433
+ {
434
+
435
+ // ネットワークに接続
436
+
437
+ }
438
+
439
+ catch
440
+
441
+ {
442
+
443
+ throw new NetworkConnectionException(); // GetDataExceptionをスロー
444
+
445
+ }
446
+
447
+
448
+
449
+ try
450
+
451
+ {
452
+
453
+ // ファイルオープン
454
+
455
+ reader = new StreamReader(_path);
456
+
457
+ }
458
+
459
+ catch
460
+
461
+ {
462
+
463
+ throw new FileOpenException(); // GetDataExceptionをスロー
464
+
465
+ }
466
+
467
+
468
+
469
+ var list = new List<string>();
470
+
471
+ //...
472
+
473
+ return list;
474
+
475
+ }
476
+
477
+ finally
478
+
479
+ {
480
+
481
+ reader?.Dispose();
482
+
483
+ networkConnection?.Dispose();
484
+
485
+ }
486
+
487
+ }
488
+
489
+ }
490
+
491
+
492
+
493
+ // ファイルのオープンに失敗したときのエラーを表す
494
+
495
+ // GetDataExceptionを投げる?
496
+
497
+ public class FileOpenException : GetDataException
498
+
499
+ {
500
+
501
+ public FileOpenException()
502
+
503
+ : base(FileOpenErrorLog.Instance)
504
+
505
+ {
506
+
507
+ }
508
+
509
+ }
510
+
511
+ // 同上
512
+
513
+ public class NetworkConnectionException : GetDataException
514
+
515
+ {
516
+
517
+ public NetworkConnectionException()
518
+
519
+ : base(ConnectToNetworkForFileErrorLog.Instance)
520
+
521
+ {
522
+
523
+ }
524
+
525
+ }
526
+
527
+
528
+
529
+ #endregion
530
+
531
+
532
+
533
+ #region IParseDataの実体
534
+
535
+ // コレクションのデータを変換するIParseDataの実装
536
+
537
+ class CollectionParser<TRawItem, TMappedItem> : IParseData<IEnumerable<TRawItem>, IEnumerable<TMappedItem>>
538
+
539
+ {
540
+
541
+ public CollectionParser(
542
+
543
+ IValidateData<TRawItem> validator,
544
+
545
+ IMapData<TRawItem, TMappedItem> mapper,
546
+
547
+ IFixedLogger logger)
548
+
549
+ {
550
+
551
+ this._validator = validator;
552
+
553
+ this._mapper = mapper;
554
+
555
+ this._logger = logger;
556
+
557
+ }
558
+
559
+
560
+
561
+ public Tuple<bool, IEnumerable<TMappedItem>> ParseData(IEnumerable<TRawItem> rawItems)
562
+
563
+ {
564
+
565
+ // 変換の成否に関する戻り値設定に使う
566
+
567
+ var inputItemsCount = 0;
568
+
569
+
570
+
571
+ // 検証と変換に成功したもののみで絞込
572
+
573
+ var parseResult = rawItems.Select(
574
+
575
+ rawItem =>
576
+
577
+ {
578
+
579
+ // 生データコレクションカウント
580
+
581
+ inputItemsCount++;
582
+
583
+
584
+
585
+ if (_validator.ValidateData(rawItem)) // 検証してみる
586
+
587
+ {
588
+
589
+ // 検証OKならマッピングしてみる
590
+
591
+ try
592
+
593
+ {
594
+
595
+ var mapped = this._mapper.MapData(rawItem);
596
+
597
+ return new { Data = mapped, Result = true };
598
+
599
+ }
600
+
601
+ catch (MapException ex)
602
+
603
+ {
604
+
605
+ _logger.Log(ex.Log); // 失敗したらエラーログ
606
+
607
+ }
608
+
609
+ }
610
+
611
+ // 検証失敗やマッピングで例外の場合は失敗を返す
612
+
613
+ return new { Data = default(TMappedItem), Result = false };
614
+
615
+ }).Where(mappingResult => mappingResult.Result).Select(mappingResult => mappingResult.Data).ToList();
616
+
617
+
618
+
619
+ // 得られたコレクションの長さで判定
620
+
621
+ return new Tuple<bool, IEnumerable<TMappedItem>>(parseResult.Count == inputItemsCount, parseResult);
622
+
623
+ }
624
+
625
+ }
626
+
627
+
628
+
629
+ // 特定のソフトが出力するログを検証し、エラーログも出力するIValidateの実装
630
+
631
+ class XXXXLogDataValidatorWithLog : IValidateData<string>
632
+
633
+ {
634
+
635
+ private readonly IFixedLogger _logger;
636
+
637
+
638
+
639
+ public XXXXLogDataValidatorWithLog(IFixedLogger logger)
640
+
641
+ {
642
+
643
+ this._logger = logger;
644
+
645
+ }
646
+
647
+
648
+
649
+ public bool ValidateData(string data)
650
+
651
+ {
652
+
653
+ var result = data == string.Empty;
654
+
655
+ // 失敗したらログを出力
656
+
657
+ if (!result)
658
+
659
+ {
660
+
661
+ _logger.Log(UnavailableDataDetectedErrorLog.GetInstance(data, "文字列が空文字"));
662
+
663
+ }
664
+
665
+
666
+
667
+ return result;
668
+
669
+ }
670
+
671
+ }
672
+
673
+
674
+
675
+ // 特定のソフトが出力するログをオブジェクトに変換するIMapの実装
676
+
677
+ class XXXSimpleMapper : IMapData<string, Something>
678
+
679
+ {
680
+
681
+ public Something MapData(string raw)
682
+
683
+ {
684
+
685
+ string name;
686
+
687
+ string message;
688
+
689
+
690
+
691
+ try
692
+
693
+ {
694
+
695
+ name = raw.Substring(0, 10).TrimStart();
696
+
697
+ }
698
+
699
+ catch
700
+
701
+ {
702
+
703
+ // ここや
704
+
705
+ throw new SimpleMapExeption("名前が変換できない", raw);
706
+
707
+ }
708
+
709
+
710
+
711
+ try
712
+
713
+ {
714
+
715
+ message = raw.Substring(10);
716
+
717
+ }
718
+
719
+ catch
720
+
721
+ {
722
+
723
+ // ここは、MapExceptionではなく、予期しないエラーになるべきか?
724
+
725
+ // 設計的にValidateでtrueが返されてる時点でここでの失敗は予期されない?
726
+
727
+ // その場合、Validateを頑張って作らないといけなくなる
728
+
729
+ // あるいは、ここでログを出力する?
730
+
731
+ // その場合返すものがなくなる
732
+
733
+ // IParseDataにかえて、ここでログをはきfalseを返す?
734
+
735
+ // そもそもIMapいる?
736
+
737
+ throw new SimpleMapExeption("メッセージが変換できない", raw);
738
+
739
+ }
740
+
741
+
742
+
743
+ return new Something(name, message);
744
+
745
+ }
746
+
747
+ }
748
+
749
+
750
+
751
+ // IMapにMapExceptionは必要?
752
+
753
+ public class SimpleMapExeption : MapException
754
+
755
+ {
756
+
757
+ public SimpleMapExeption(string errorReason, string errorData)
758
+
759
+ : base(UnavailableDataDetectedErrorLog.GetInstance(errorReason, errorData))
760
+
761
+ {
762
+
763
+ }
764
+
765
+ }
766
+
767
+
768
+
769
+ #endregion
770
+
771
+
772
+
773
+ #region ISendDataの実体
774
+
775
+ // DBに送信するISendDataの実装
776
+
777
+ class SenderToMyDatabase : ISendData<IEnumerable<Something>>
778
+
779
+ {
780
+
781
+ public void SendData(IEnumerable<Something> data)
782
+
783
+ {
784
+
785
+ try
786
+
787
+ {
788
+
789
+ // データを保存
790
+
791
+ }
792
+
793
+ catch
794
+
795
+ {
796
+
797
+ // 送信失敗のLog入りでSendDataExceptionをスロー
798
+
799
+ }
800
+
801
+ }
802
+
803
+ }
804
+
805
+
806
+
807
+ #endregion
808
+
809
+ ```コードをとりあえず載せておきます。
810
+
811
+ 突っ込みどころがあれば指摘してください。
812
+
813
+ コンパイル可能には、文字数の関係で無理でした。
814
+
815
+ 後ほど追記します質問にご意見いただければ幸いです。
816
+
817
+ よろしくお願いいたします。

2

追記

2017/01/22 15:28

投稿

syogakusya
syogakusya

スコア67

test CHANGED
File without changes
test CHANGED
@@ -60,24 +60,80 @@
60
60
 
61
61
 
62
62
 
63
- #追記
63
+ #追記2
64
64
 
65
- 回答ありがとございます。
65
+ 皆様からいただきました意見を総合し、現在以下のよなコードにつて考えています。
66
66
 
67
- いただいた意見から、まずはインターフェースを変更する方向で以下のように考えてみることにしました。
67
+ ```C#
68
68
 
69
- これについて意見をください。
69
+ class TestClass<TData, TParsed>
70
70
 
71
- まず、ここで使用されている各インターフェースの戻り値をTからTuple<bool?,T>に、voidからTuple<bool?>に変更し、各インターフェースは処理が中断するかどうかをTuple.Item1に設定するようにしました。trueは正常終了,falseは異常終了,nullは中断しないことを示すものとします。戻り値があったものはTuple.Item2にその値が設定されます。
71
+ {
72
72
 
73
- また、Testクラスは各インターフェースのもつメソッドを実行した後、Tuple.Item1の値を判定し、値がnullでなければ処理を中断するようにします。
73
+ public TestClass(IGetData<TData> getter, IParseData<TData, TParsed> parser, ISendData<TParsed> sender)
74
74
 
75
- また、最後に実行されるインターフェースのメソッドの戻り値については、nullまたはtrue(異常終了以外)であれば正常終了と考えるようにします。
75
+ {
76
76
 
77
- ログの出力などは各インターフェースのデコレーターを作成して行うようにします。
77
+ // ...
78
78
 
79
- つまり、Testクラスの実装と各インターフェースのシグネチャを中断を想定したものに変更し、正常終了も異常終了も同じようにその仕組みで扱います。例外は投げないようにします。
79
+ }
80
80
 
81
+
82
+
83
+ public void Do()
84
+
85
+ {
86
+
87
+ var raw = default(TData);
88
+
89
+ try
90
+
91
+ {
92
+
93
+ raw = _getter.GetData(); // GetDataは基本成功
94
+
95
+ }
96
+
97
+ catch (GetDataException) // GetDataがGet失敗した時に出す例外「だけ」をcatch
98
+
99
+ {
100
+
101
+ var log = CreateLog("ファイルオープン失敗");
102
+
103
+ throw new FatalErrorException(log); // 致命的なエラーを例外で表現する
104
+
105
+ }
106
+
107
+
108
+
109
+ var parsed = _parser.ParseData(raw); // ParseDataは例外を出さない
110
+
111
+
112
+
113
+ if (!parsed.Success) // parseが失敗した場合は
114
+
115
+ {
116
+
117
+ var log = CreateLog("データが〇〇社のデータ変換ソフトウェア『××』では変換できません。");
118
+
119
+ throw new FatalErrorException(log); // ログ入りスロー
120
+
121
+ }
122
+
123
+
124
+
125
+ _sender.Send(parsed.Data);
126
+
127
+ }
128
+
129
+ }
130
+
131
+
132
+
133
+ ```
134
+
135
+ しかし、このコードですとDoメソッドがIGetDataやIParseDataの実装について知っていることになってしまいます。
136
+
81
- 帰宅次第コードを作成して記載しうと思います。
137
+ このコードをよくするための方針につて悩んでいます。
82
138
 
83
139
  引き続きよろしくお願いいたします。

1

2017/01/21 06:05

投稿

syogakusya
syogakusya

スコア67

test CHANGED
File without changes
test CHANGED
@@ -57,3 +57,27 @@
57
57
  その場合、異常終了するためには必ず例外をスローしなければなりませんが、例えばファイルがない場合に正常終了ではなく異常終了したくなった場合、ファイルがなければ普通に例外をスローしてエントリポイントで捕捉するようなことになると思います。
58
58
 
59
59
  よろしくお願いします。
60
+
61
+
62
+
63
+ #追記
64
+
65
+ 回答ありがとうございます。
66
+
67
+ いただいた意見から、まずはインターフェースを変更する方向で以下のように考えてみることにしました。
68
+
69
+ これについて意見をください。
70
+
71
+ まず、ここで使用されている各インターフェースの戻り値をTからTuple<bool?,T>に、voidからTuple<bool?>に変更し、各インターフェースは処理が中断するかどうかをTuple.Item1に設定するようにしました。trueは正常終了,falseは異常終了,nullは中断しないことを示すものとします。戻り値があったものはTuple.Item2にその値が設定されます。
72
+
73
+ また、Testクラスは各インターフェースのもつメソッドを実行した後、Tuple.Item1の値を判定し、値がnullでなければ処理を中断するようにします。
74
+
75
+ また、最後に実行されるインターフェースのメソッドの戻り値については、nullまたはtrue(異常終了以外)であれば正常終了と考えるようにします。
76
+
77
+ ログの出力などは各インターフェースのデコレーターを作成して行うようにします。
78
+
79
+ つまり、Testクラスの実装と各インターフェースのシグネチャを中断を想定したものに変更し、正常終了も異常終了も同じようにその仕組みで扱います。例外は投げないようにします。
80
+
81
+ 帰宅次第コードを作成して記載しようと思います。
82
+
83
+ 引き続きよろしくお願いいたします。