Существуют ли наработанные подходы к интеграции отладки в код?
Если существуют, то какие?
Если не существует, то есть ли какие-нибудь идеи?
Узнал, что в visual-studio
можно жестко прописывать в коде точки останова. Пришла идея объединить вызов точки останова из кода с выводом в output
. Причем, с выводом названия метода и номера строки. То, что получилось напишу в ответе. Сразу помогло найти какие-то ошибки в своем коде.
Целесообразно ли вообще использовать подобный подход?
Целесообразно ли повсеместно закладывать подобный подход на этапе до отладки?
Чего не достает в описании концепции или как можно ее усовершенствовать или что лишнее?
Отчасти основано на вызове Debugger.Break()
. Если кратко, то использовать можно так:
if (this.Debug())
Debugger.Break();
Механизм:
Интерфейсом IWithBreakpoints
могут быть наделены классы, чтобы можно было использовать механизм:
public interface IWithBreakpoints
{
bool EnableBreakpoints { get; set; } // локальный флаг вкл/откл
int NestingLevel { get; } // уровень вложенности
string GetDebugInfo(); // строка с инфо об объекте
}
Сам Breakpoints
, отчасти основан на extension-методе-расширении bool Debug()
, в котором одновременно делается (если надо) вывод инфо в output
и возвращается флаг необходимости вызова точки останова:
public static class Breakpoints
{
public static bool Enabled { get; set; }
public static bool WritingEnabled { get; set; }
static Breakpoints()
{
Enabled = false;
WritingEnabled = true;
}
public static bool Debug(this IWithBreakpoints @object)
{
bool shouldWrite = false;
bool shouldBreak = false;
#if DEBUG
shouldWrite = WritingEnabled;
&& @object.EnableBreakpoints
;
shouldBreak = Enabled
&& @object.EnableBreakpoints
&& Debugger.IsAttached
;
#endif
WriteLineIf(shouldWrite, @object.GetDebugInfo(), @object.NestingLevel);
return shouldBreak;
}
// ---------------------------------------------
// .... реализации WriteLine(..) основанных на Debug.WriteLine(..)
// .... с добавлением названия метода и номера строки
}
Если более подробно, то использовать так:
public class TreeItem : IWithBreakpoints
{
public string Name { get; } = Guid.NewGuid().ToString().Substring(1,4);
public TreeItem Parent { get; set; }
public List<TreeItem> Children { get; } = new List<TreeItem>();
public TreeItem(TreeItem parent, int childrenCount)
{
NestingLevel = parent != null ? parent.NestingLevel + 1 : 0;
if (this.Debug())
Debugger.Break();
Parent = parent ?? null;
if (childrenCount == 0)
return;
for (int i = 0; i <= childrenCount-1; i++ )
{
Children.Add(new TreeItem(this, childrenCount - 1));
}
if (this.Debug())
Debugger.Break();
}
public void Traverse(Action<TreeItem> action)
{
if (this.Debug())
Debugger.Break();
action(this);
foreach(var child in this.Children)
{
child.Traverse(action);
}
}
// --------------------------
public bool EnableBreakpoints { get; set; } = true;
public int NestingLevel { get; set; }
public string GetDebugInfo()
{
return $"{Name} (ChildrenCount={Children.Count}): ";
}
}
И так:
class Program
{
static void Main(string[] args)
{
Breakpoints.Enabled = true;
Breakpoints.WritingEnabled = true;
Debug.WriteLine("------------------------------------");
var item = new TreeItem(null, 3);
Debug.WriteLine("------------------------------------");
item.Traverse(it => Console.WriteLine(it.Name));
Console.ReadLine();
}
}
Тогда, остановившись на каждой Debugger.Break()
, программа выведет в output
следующие строки:
------------------------------------
#### [02:58:59.823]: 0025 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:03.498]: . 95d4 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:04.360]: . . 1136 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:05.321]: . . . d8aa (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:06.152]: . . 1136 (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 31
#### [02:59:07.056]: . . 2585 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:07.698]: . . . 6b50 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:08.273]: . . 2585 (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 31
#### [02:59:08.921]: . 95d4 (ChildrenCount=2): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 31
#### [02:59:09.592]: . f608 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:10.140]: . . 1d3e (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:10.720]: . . . 89ca (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:11.300]: . . 1d3e (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 31
#### [02:59:11.891]: . . 1e46 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:12.554]: . . . b09a (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:13.511]: . . 1e46 (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 31
#### [02:59:14.314]: . f608 (ChildrenCount=2): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 31
#### [02:59:15.105]: . ce17 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:15.657]: . . 67c2 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:16.129]: . . . a049 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:16.681]: . . 67c2 (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 31
#### [02:59:17.207]: . . 5827 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:17.681]: . . . 2c55 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 20
#### [02:59:18.144]: . . 5827 (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 31
#### [02:59:18.777]: . ce17 (ChildrenCount=2): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 31
#### [02:59:19.425]: 0025 (ChildrenCount=3): #### : в BreakpointsIdea.TreeItem..ctor(TreeItem parent, Int32 childrenCount) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 31
------------------------------------
#### [02:59:23.251]: 0025 (ChildrenCount=3): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:26.377]: . 95d4 (ChildrenCount=2): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:27.407]: . . 1136 (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:28.081]: . . . d8aa (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:28.865]: . . 2585 (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:29.591]: . . . 6b50 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:30.298]: . f608 (ChildrenCount=2): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:31.002]: . . 1d3e (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:31.616]: . . . 89ca (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:32.184]: . . 1e46 (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:32.867]: . . . b09a (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:33.465]: . ce17 (ChildrenCount=2): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:33.961]: . . 67c2 (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:34.327]: . . . a049 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:34.617]: . . 5827 (ChildrenCount=1): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
#### [02:59:34.820]: . . . 2c55 (ChildrenCount=0): #### : в BreakpointsIdea.TreeItem.Traverse(Action`1 action) в C:\Users\User\source\repos\BreakpointsIdea\BreakpointsIdea\Program.cs:строка 37
По вопросам:
Существуют наработанные подходы или нет: не знаю, сходу не нашел.
Какие: не знаю.
Есть ли какие-нибудь идеи: то, что в ответе.
Целесообразно ли вообще использовать подобный подход: думаю, иногда можно.
Целесообразно ли повсеместно закладывать: не знаю.
Чего не достает: если таких точек много, то как их лучше отключать, или наоборот, как включать только нужные? Если строку GetStackTraceLine()
использовать как ключ, то можно было бы организовать временный tmp файл, где хранить флаг включен/отключен для каждой такой точки останова. И менять его при остановке прямо в Quick-Watch
? По истечении времени чтобы эти точки сами дисэйблились? Или это навороты?
GetStackTraceLine()
:
private static string GetStackTraceLine()
{
using (StringReader sr = new StringReader(Environment.StackTrace))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if (line.Contains("."+nameof(Breakpoints)+".")
|| line.ToLower().Contains(nameof(Environment.StackTrace).ToLower())
|| line.Contains(nameof(Environment)))
continue;
else
return line;
}
return string.Empty;
}
}
Весь код механизма:
public interface IWithBreakpoints
{
bool EnableBreakpoints { get; set; } // локальный флаг включения отключения механизма
int NestingLevel { get; } // уровень вложенности
string GetDebugInfo(); // строка с информацией об объекте для вывода
}
public static class Breakpoints
{
public static bool Enabled { get; set; }
public static bool WritingEnabled { get; set; }
static Breakpoints()
{
Enabled = false;
WritingEnabled = true;
}
public static bool Debug(this IWithBreakpoints @object)
{
bool shouldWrite = false;
bool shouldBreak = false;
#if DEBUG
shouldWrite = WritingEnabled;
&& @object.EnableBreakpoints
;
shouldBreak = Enabled
&& @object.EnableBreakpoints
&& Debugger.IsAttached
;
#endif
WriteLineIf(shouldWrite, @object.GetDebugInfo(), @object.NestingLevel);
return shouldBreak;
}
// ---------------------------------------------
// .... WriteLine(..) implementations (appends method name and line number from Environment.StackTrace)
private static string GetStackTraceLine()
{
using (StringReader sr = new StringReader(Environment.StackTrace))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if (line.Contains("."+nameof(Breakpoints)+".")
|| line.ToLower().Contains(nameof(Environment.StackTrace).ToLower())
|| line.Contains(nameof(Environment)))
continue;
else
return line;
}
return string.Empty;
}
}
private static void WriteLineBase(string line)
{
if (WritingEnabled)
System.Diagnostics.Debug.WriteLine(line);
}
private static void WriteLine(string line)
{
WriteLine(line, 0);
}
private static void WriteLine(string line, int indent)
{
var tabs = new StringBuilder();
for (int i = 0; i < indent; i++)
{
tabs.Append(". ");
}
tabs.Append(line);
WriteLineBase(FormatLine(tabs.ToString()));
}
private static void WriteLineIf(bool condition, string line)
{
if (condition)
WriteLine(line, 0);
}
private static void WriteLineIf(bool condition, string line, int indent)
{
if (condition)
WriteLine(line, indent);
}
private static string ToTimeStringWithMilliseconds(this DateTime dateTime)
{
return dateTime.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture);
}
private static string FormatLine(string line)
{
return $"#### [{DateTime.Now.ToTimeStringWithMilliseconds()}]: {line} #### : {GetStackTraceLine()}";
}
}
И, на всякий, код примера:
public class TreeItem : IWithBreakpoints
{
public string Name { get; } = Guid.NewGuid().ToString().Substring(1,4);
public TreeItem Parent { get; set; }
public List<TreeItem> Children { get; } = new List<TreeItem>();
public TreeItem(TreeItem parent, int childrenCount)
{
NestingLevel = parent != null ? parent.NestingLevel + 1 : 0;
if (this.Debug())
Debugger.Break();
Parent = parent ?? null;
if (childrenCount == 0)
return;
for (int i = 0; i <= childrenCount-1; i++ )
{
Children.Add(new TreeItem(this, childrenCount - 1));
}
if (this.Debug())
Debugger.Break();
}
public void Traverse(Action<TreeItem> action)
{
if (this.Debug())
Debugger.Break();
action(this);
foreach(var child in this.Children)
{
child.Traverse(action);
}
}
// --------------------------
public bool EnableBreakpoints { get; set; } = true;
public int NestingLevel { get; set; }
public string GetDebugInfo()
{
return $"{Name} (ChildrenCount={Children.Count}): ";
}
}
class Program
{
static void Main(string[] args)
{
Breakpoints.Enabled = true;
Breakpoints.WritingEnabled = true;
Debug.WriteLine("------------------------------------");
var item = new TreeItem(null, 3);
Debug.WriteLine("------------------------------------");
item.Traverse(it => Console.WriteLine(it.Name));
Console.ReadLine();
}
}
Updates:
Upd1:
output
: System.Diagnostics.Debug.WriteLine(line)
.IsEnabled
, чтобы у таких объектов был ключ, а потом по ключу прописывать (или хранить) enabled/disabledthis.Debug()
можно было бы сделать перегрузку this.Debug(string debugInfo)
, которая бы вместо стандартной строки объекта писала бы конкретное сообщениеЛично я не представляю себе, где бы мне в реальных сценариях понадобилась глубокая интеграция отладки в код приложения. Кому она может понадобиться? Пользователю у которого на компьютере нет студии? Нужно просто писать приложение хорошо и модульно, не забывать писать юнит-тесты из которых вполне себе доступна не только кнопка Run, но и Debug - и можно провалиться внутрь исполняемого кода и посмотреть вживую, что там происходит.
Подобный уровень интеграции уже есть из коробки и его на компьютере разработчика вполне достаточно.
Я много прототипирую кода в linqpad перед тем, как вносить его в студию и достаточно часто у меня бывает необходимость из кода в linqpad провалиться в код проекта, написанный в студии и продолжить там отладку.
В таком случае мне вполне достаточно добавить одну строку в начале скрипта:
//Debugger.Launch();
Из моего другого ответа можно выбрать только метод WriteLine(string)
, который сам добавляет в конце строчки имя класса и метода - инфу из StackTrace-a (так же выбрать сопутствующие другие методы).
Далее поверх вызова этого WriteLine поставить обыкновенную студийную точку останова.
Тогда и получается реализация задумки, то есть, при остановке в отладке можно посмотреть StackTrace, а так же по логам в output тоже что-то можно посмотреть.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
как компилировать программу wpf без студии? пишу в консоли:
Всем привет, решил написать программу для обработки донатов на donationalerts, работают они через виджет в виде страницы и через F12 я понял что получают...
Всем привет, прошу помочь в решение очень сложного вопроса (Мучаюсь с ним уже 4 дня)
Недавно узнал о паттерне DTO, до этого в приложении мои методы возвращали массивы - и это был ад их рефакторитьТеперь понял, что этот паттерн...