T O P

  • By -

Design-Cold

Debugging is hands down the best way I understand what's going on. Set a breakpoint in a chunk of code and step through it.


nullandkale

Also once the debugger has stopped the code somewhere you can look at the stack viewer and go up the stack to see what's calling the code you are looking at. This is a great way to find the more high level structures in the program.


wannabeAIdev

Junior here, this is great advice and I'll be using it


Sossenbinder

That's the best way. Get your hands dirty and keep a markdown handy to document everything you found confusing or difficult


joshjje

I second this. Solid debugging skills are hugely helpful, conditional breakpoints, action breakpoints that just print messages to the output window. Also effective navigation, ability to quickly find things either by just find all on the code base or find references, ability to decompile third party code to see what it is doing.


rockseller

Sometimes you get code base that can't be debugged as it depends on other external piece of code or it's a piece of another puzzle. I'd say diagram flows starting from high level to low level


Design-Cold

If you can't debug your code base then I suggest you need to either fix that or abandon your role, debugging is table stakes


t4thfavor

I think it refers to external binary libraries that can sometimes be black boxes of magic.


emorning

Highly asynchronous code, like you might find in event driven environments like a UI, is damn near impossible to step through. In these cases I prefer some kind of logging or tracing to understand what's going on. Such logging or tracing should be conditionally compiled, so you can turn it on when testing or debugging.


Design-Cold

I've stepped through WPF Winforms and [ASP.NET](http://ASP.NET) code all the time it's *fine* As an aside if you've somehow designed a solution that's impossible to debug by stepping through I'd argue that's the same as building an office without fire exits.


emorning

I do a lot of Blazor UI work. I often find it difficult to diagnose issues in highly reactive applications because there are often a lot of Tasks doing stuff, and you can't just step through them. But let me offer a different example... what if the code that the OP needs to grok is a distributed system? That's another example of an async environment where I find logging is more useful than debugging. Because, in this case, you literally can't step through the code since the code is in other processes or on other machines. I agree that highly asycn code \*can be\* just poorly written code, but sometimes is just the nature of the beast.


Design-Cold

I'm not sure why you're attempting to think up scenarios where you can't step through code but nobody's upset about you logging stuff as long as it's useful stuff and ALSO correlated on some kind of ID because there's nothing worse than looking through a log spew of two hundred people making API calls trying to work out which one's your guy


[deleted]

[удалено]


DeepPurpleJoker

Also works in linq. You step rows. So break linq into multiple rows…


decPL

This is a perfect valid answer as of, around, 2010 - debugging LINQ code back then was a nightmare, because breakpoints/stepping in didn't work well with lambdas. In our current time it's pretty smooth.


Design-Cold

you mean in query syntax? Still works for viewing, although I think edit and continue isn't enabled in that case Which is a shame, edit and continue is fricken magical


nullandkale

The non-debugablilty is yet another reason to not use linq


Suspect4pe

Just take it a little at a time. Understand what you need to make the change you want and work out from there. Sometimes we never get the big picture but we get enough to do what we need.


binarycow

One piece at a time. Basically, pick a starting point, and branch out from there. There are a couple of different ways to pick a starting point. Often, you'll start somewhere, learn some stuff, and then get lost. So then you pick another starting point, and then explore from that angle. Now, if you go back to the beginning again, you might be able to understand stuff you didn't understand before. Basically, you're going to go back and forth. A lot. So, one technique is to find the "entry point". The entry point could take a couple of different forms. Applications will use Program.cs, Startup.cs, App.xaml, etc. For a library, it might be some other class (e.g., for a JSON serializer library, it would be the `JsonSerializer` type). Review the DI setup (if applicable). Try to get a sense of the overall workflow of the app. In some apps, the entry point might be really simple. WinForms for example, the main method is like three lines long - but from that you can determine which form is the main form for the application. Another technique is to look at the solution from a high level. Look at how the different projects interact with each other. See which projects depend on the others. Look at the folder structure. Get a general sense of how things fit together. etc... Another technique is to try to understand one project at a time. Pick a project that has no dependencies on other projects (nuget packages are okay). Learn that project. Then move on to another. Use your tools to help you. Here's a list of only some of the tools available to you: - Rider - [Project depdency diagram](https://www.jetbrains.com/help/rider/Architecture__Project_Dependencies_Exploration.html) - ReSharper - [Project dependency diagram](https://www.jetbrains.com.cn/en-us/help/resharper/Architecture__Project_Dependencies_Exploration.html) - [Type dependency diagram](https://www.jetbrains.com.cn/en-us/help/resharper/Exploring_Type_Dependency_Graph.html) - [Inspect hierarchies](https://www.jetbrains.com.cn/en-us/help/resharper/Navigation_and_Search__Viewing_Type_Hierarchy.html) - Visual Studio: - [Code maps](https://learn.microsoft.com/en-us/visualstudio/modeling/map-dependencies-across-your-solutions?view=vs-2022) (Enterprise edition only) - [Solution Dependency Viewer](https://marketplace.visualstudio.com/items?itemName=VisualCppDevLabs.SolutionDependencyViewer) (extension) - VS Code - [Dependency Graph](https://marketplace.visualstudio.com/items?itemName=sz-p.dependencygraph) (extension)


TuberTuggerTTV

Right click => Go to definition


Avalant

Keep doing it until you hit bedrock


no_real_dinner

F12 for life.


socar-pl

Top-Down approach Each application either have a set of user features (client used) or scenarios (automated/batch) that it covers. I try to take few common scenarios that application handle and replay them in isolated/test environment - if possible - line by line from start to finish. That is a good time to comment stuff while you go along the code (if code is lacking documentation). If possible either as live debugging session, analyzing logs or both. It's not common for me to have wireshark running in background to see (unencrypted) network communication if app support it. In case of isolated apps where you have no access code on hand you have to rely on Sysinternals DebugView, internal logs, compmgmt.msc at the same time reflectoring out of app whatever you can. Looking at test cases (assuming app have tests) is also good option. Analyzing data storage might give you some pointers. In past my approach was to have automated tool build object dependency map between classes, but it was not really telling story how app operates without having a scenario on hand (and later on I got app which dependency injection pattern was controlled from outside of the app - by xml so modules could be loaded without need of rebuilding the core assembly, so object map lost it's sense at all). More or less this is a research process that sometimes need even a piece of paper to scribble diagrams and such, depending on your learning and information retaining preferences.


erbaker

This is my method as well. If it was built well then it's no big deal, but sometimes you have to use bookmarks or other markers to step around. Ideally start at (for example) the API method and then work your way down, opening tabs and marking lines as you go. That should get you 80% of the way to understanding


Slypenslyde

If they use standard patterns and conventions? I follow the patterns because reading their code feels like my code. If they left good documentation? I use that to help me figure out where what I want is. Neither? I crack open a new text file, pick a file that seems close to what I'm trying to learn, and start taking my own copious notes as I navigate through the call chains. If it's a GUI app, web or desktop, the thing I want is affiliated with a View. Finding that View is usually easy unless the project has no organization. From there I can start looking at dependencies, or at least a list of methods, etc. For pure backend, sometimes I just have to start at Program.cs and keep going. If it's using a convention-based framework that makes it harder. But if it's using a convention-based framework I can learn the conventions to figure out what I'm not seeing, and in those cases it's more likely I can skip to what I want by searching for a file with the feature name in its name.


CaitaXD

OOP code base? You will have to jump to definition a lot.


already-registered

My uncle waa freelance developer and often had new projects. He told me whenever he got a new gig he copies the solution or entire repo, goes through all the code and comments it for himself. The writing down of things apparently helped him to memorize and understand stuff faster. I always wanted to do this, but I am also not freelance and dont have the drive and time to do it correctly. However I still think it sounds like a great idea to really understand stuff. The brain is activated the more you use all your senses. Just sitting and reading has less effect then than writing your own thoughts down.


TheXenocide

I'll add "View Call Hierarchy" to the list of suggestions here.


Kundelstein

My job consist mostly of fixing and updating someone else's code. I usually do a "messy" copy and I rename variables and classes a lot (yes, within the fresh git I commit a lot with long descriptions}. In such case any diff view (Fork or winmerge for me) gives easy overview of the reach of code data. Basically it helps me to map all code in my head. It can take up to a few days to familiarize myself with medium sized project (50 to 500 lines). Such approach gives me a freedom to mess things up as afterwards I bury the code in the cemetery anyway.


Yelmak

Working on code where people actually care about readability and maintainability really helps. Mastering the IDE is also a good idea, being able to easily jump into methods, search a method's references, debug effectively, etc.


[deleted]

Absolutely. Trying to write code that only deals with the 'what' rather than the 'how' at the top-level, then delving into implementation when it becomes necessary


faculty_for_failure

When I start a new position, I usually spend the first few weeks reading code and running it and debugging it.


Euvu

Lots of great suggestions here. Only thing I'll add is if you have access to GitHub copilot, then you can use the GitHub copilot chat extension in visual studio. You can highlight code and ask it what the code does. It's not perfect, but it can be a really useful place to start. YMMV


LutadorCosmico

[Roslyn ](https://github.com/dotnet/roslyn)to rescue. You can load a solution/projects and capture the methods call hierarchy, what calls what, you can then build reports from it. There will be challenges on interface resolution and other hard corners but overall you get some nice data to follow along. Another aproach is to use something like [MonoCecil ](https://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/)to extract it from assembly level, instead of source level.


TarnishedVictory

If it's really complex, it sometimes helps to diagram things out with some flow charts or similar to help get a big picture of flow.


AnotherCannon

For really gnarly code with multiple files, it’s useful to go old school and record your findings in a notebook. Write down the name of the file, the line number, what it’s doing and what it touches this will help for those situation, or somebody comes up and asked you a question, and ends of breaking your concentration.


Prestigious_Carpet60

When I start on a new codebase I like to get some bug tickets and fix them.


Impossible_Raise_817

Start with entry point of application.


EarthWormJimII

I will typically tell new devs to debug specific buttons or APIs which I know to be simple but passing through all relevant layers.


ivancea

I usually try to map features (api calls, user flows...) to code domains, and try to understand the folder/naming structure. After that, following entrypoints, and seeing the chain of calls. For how specific functions work, debugging is usually the faster way, after checking it thoroughly


Bobbar84

I find the tools built into Visual Studio priceless for navigating big ass projects, even if it's not C# and I can't even compile it locally. Just being able to quickly find definitions, their implementations, and references, and do text searches usually gets me pretty quickly up to speed about what the hell is going on. Look at examples and documentation for the project (if it exists) and see what methods and classes they mention and take a look at those first. At any rate, practice is a big factor too.


Knut_Knoblauch

My current project is very difficult. One of my hardest. I have inherited a C# application that was originally a Pascal application. It became C# through an application converter. The Pascal version had custom delphi compiled units for SQL communication to a SQL Server. We all know that C# and SQL Server applications at scale should be done in Entity Framework. It just makes sense when there a multi-users logged in and doing T-SQL. Not with this app. The conversion app faithfully brought over the SQL and low level code manages it. To get an idea of what that means, it takes 9 seconds to load up a "file" of information. Under the hood some 700,000 lines of SQL have been read, processed, and cached up and then managed. The conversion was not without its own problems and bugs were introduced. Some very serious flaws. The most glaring is that the conversion put code in areas where the IDE puts its own generated code. It can be hard to tell which belongs to the IDE and which the converter. The end result is that when a form is edited visually, Visual Studio, at the time of saving, strips out foreign code that it did not place. This makes it so that I have two copies of the code loaded up in two sessions. One that lets me look at the forms and one that lets me work on them. There are a ton of problems. All of the eventing is done with the strangest logic. The code looks at the text of the control where an event is triggered to determine what to do. This is decidedly fragile and prone to errors and code reuse. I've found places in the code where a keystroke from a person is simulated to trigger the event. This is one of my hardest contracts, but it is relatively short term. In my heart of hearts, I feel like the bugs will never cease to pop up. It has been challenging and pushing my debugging ability to the limits. Just today, I had to deal with a combo box that had both a databinding and also a datasource. A datagrid was also tied to the binding of the combo box but only with a reduced set of rows of data. It didn't work. I had to redesign the interaction of those controls. It sucks because I don't have the history or too much time to get used to the code base.


KSP_HarvesteR

Go to Definition, and Find all References. I have them mapped to macro keys on my left pinky. They are the two most important navigation tools you have.


GYN-k4H-Q3z-75B

You need to get a bird's eye view of the code base, understand the general architecture and idea first. Is there some kind of high-level documentation you can use to understand it? If not, you will have to learn from the code itself, which tends to take longer. Debugging actions can be very helpful as the code path will show you the architecture.


chrisdpratt

It can be difficult, depending on what you're dealing with. You can write spaghetti code with C# as easily as anything else. Ideally, you'll have interfaces and abstract classes to lean on to get the jist of what most things are doing. Assuming it's in a runnable state, debugging and stepping through the code can help a lot as well. That will let you see the interactions that are taking place under the hood and across the code base. If you're really lucky, the application will be broken up into layers and various libraries, so you can attack it piece meal, familiarizing yourself with the smaller pieces, before looking at the larger ones. In short, I don't think there's any magic method here. You work with what you have and use the tools you have available to make it as efficient as possible.


jcradio

Stepping through the code has always helped me. Allows me to see where code executes when I use a feature. Over time, it clicks. Well organized code with clear naming make it a lot easier.


Shrubberer

I'm currently maintaining an enormous Delphi legacy code base and I have no idea what's going on, generally speaking. When I get a ticket I usually guesstimate the modules which might be involved and put a breakpoint into the constructor. When I finally hit a breakpoint, I navigate the callstack until I find the bad call. Fixing these bugs takes similar efforts and overall I'm pretty much specialized in any parts of the code base I have touched so. Gradually I'm beginning to understand it more. This code base was a one-man project in a span of 10 years. The guy left no documentation either. One immediately did when I got to maintain it, was to write an code analyzer. I pulled out meta information like inheritance trees and package dependencies and built a gui to browse the codebase this way. This is incredibly helpful as the Delphi IDE is dogshit.


Appropriate_Wafer_38

From where the Exception was thrown, start back tracking and F12 is your best friend.


jugalator

You begin the day praying for richly annotated UML diagrams for the databases, a library of clean Markdown documentation and invigorating Swagger documented web services... Then go home tearing your hair and crying ;)


elderron_spice

To be honest, you'll probably not going to know how the entire estate is tilled for some time, especially if you're frequently jumping around companies with huge enterprise applications or projects. It's best to start with learning one business process at a time, Ctrl+F12 and Shift+F12 will do the rest. For example, if you need to change something to an API and you're working on a UI component that talks to that, then a few minutes of exploring should give you the endpoint, then you can just go to the controller, and from there to the service layer or another library, etc, etc, you know the drill.


decPL

I know this might not have been the answer you want to hear, but from my personal pov, on top of what everyone else wrote (most of this very sensible advice, especially the bits about debugging and navigating), unfortunately a huge factor here is experience - the more similar reverse engineering sessions you've had to do in your career, the better you'll be at it.


Enigmativity

"to clique"


nukeyocouch

Follow the flow


B15h73k

I make a Visio diagram. Lines and boxes. Big box for a class with smaller boxes inside for methods. Draw lines to connect methods to the methods they call. You can't map out the entire application. Just do it for the part you're working on. Simple example: api controller -> service -> repository -> database table. I've worked on code bases where the user clicking on a button in the UI results in a chain of 10-20 method calls. No way I'm going to understand that by just reading code. Mapping it out visually helps me reason about what's going on.


neilhighley

Break it down. Write unit tests on classes, follow the data


RandallOfLegend

In visual studio, I use the Object Explorer. Basically just drilling into the class structures, functions, even etc. unless it's gone off the OO deep end it's a nice way to navigate capabilities.


addys

NDepend is a great tool for analyzing complex codebases top-down. It's very niche - once every few years I'm super-happy that it exists, and then forget about it again - but for the stuff it does, it's amazing. [https://www.ndepend.com/docs/getting-started-with-ndepend](https://www.ndepend.com/docs/getting-started-with-ndepend)


neppo95

Ctrl Click. Apart from that. A lot of the time, you don't need to understand the whole code base. You just need to understand what you are working on and the effects it may or may not have on other parts of the software. There are ofcourse cases where what you are working on affects a big part of the codebase.


LogaansMind

I answer a few basic questions, what type of product is it (web, desktop etc.), what kind of technologies does it use. I then look at the libraries, what do they do. Then I look for the obvious layers, where is the UI, wheres the business logic, wheres the data access layer etc. And then, what I like to do to help my dive in and start working is I pick through backlog bugs and see if I can fix them. If I cannot do them, I chuck them back into the backlog until I do. Hopefully I help the team resolve bugs, I am going to be a productive but slow member of the team for a while until I can start picking up some of the trickier bugs. I get to see various aspects ofthe system, coding styles etc. Until I can then start implementing new features. I use this technique with all of my juniors, I find it works quite well (often when it is difficult to get juniors productive quickly). I don't put any pressure on them to achieve, even in failure we learn. Or someone might learn something specific about an issue which helps the team solve the issue faster etc.


FitzelSpleen

With more ease than many other languages... But one of the strategies I use is to get some paper and draw out "maps" of how the different classes and components interact. It's a bit like UML, but not nearly as formal. Then you get moments of understanding like "oooh, the FooWidget has a collection of BarFactory, but is in turn created by the BazController! Don't know why they did it that way, but now I understand what it is at least!"


Own_Possibility_8875

You just need to have a comprehensive, clear, unclouded outlook on the codebase. In other words, you need to *see sharp*. I’ll see myself out.


BL1NDX3N0N

Understanding what others have done and the reason behind it is a difficult task for most, especially seniors. This is why large teams have meetings to try and break up the monotony. If approaching the problem by yourself then you can only rely on yourself or whatever documentation the author has provided. Generating class diagrams or whatever visual hierarchy to gain a better understanding can be beneficial but that’s only half of the problem, the other half comes down to code paths and control flow. All programs, regardless of utilities to games, tend to start with a linear structure that branches off somewhere, and you can view this by inspecting the entry-point and going down the rabbit hole. As others have mentioned, debugging is a very useful tool. You can either drop a breakpoint at the entry-point or wherever you want to gain an in-depth understanding.


madman1969

I currently work in a support role for a big mainly undocumented C# codebase, where most of the time I'm looking/working on the same 10-20% of code. When I'm forced to deal with the remaining code I tend to find an entry point and work down from there, adding code comments as to what I think the code is doing and why. I think the best approach depends on the individual developer, some find visual tooling like that found in Visual Studio Enterprise or Rider useful, or 3rd party tools like NDepend. For me the deep-dive/commenting approach seems like the right balance. Once I feel like I've got a handle on the section of code I've been looking at I tend to add logging and unit tests as they're normally missing. Which ever approach suits you best, avoid making logic changes until you're happy you understand both the 'how' and the 'why' of the code. If there's some odd logic that does seem to click, you're likely missing some nuance. I've also found mind-mapper software to be useful as you can build out your understanding to 'plug in the gaps' as your knowledge grows.


littleGuyBri

It’s 2024 and AI can help - buy Copilot and ask it to explain the code to you, it usually does a great job and will help you learn about anything about the code


angedelamort

Here's how I do it: - first, understand what this code base does. If it has UI, test the application, check the apis, etc. - once you have some knowledge, go through the folders, filenames, the hierarchy, etc. This will give you a good summary of how the code is organized and what does it do. - after that, check for external resources: db, config files, dependencies, AWS/gcp/azure services, etc. this will help you understand where the data comes from and where it's going. Now you should have a good knowledge of the high level architecture of the base code. From there, depending on what you want to do, you have many different options. Reading code is nice and fast and from the previous steps, or should be pretty easy to decide where to start for what you want to investigate or fix. The debugger is your friend. If you think the code there should be executed, it will break there. You can also just do steps from the entry point, which is always a good place to start. Now with AI, you can also ask some explanations :)


neroe5

patterns, patterns, patterns the best skill of the human mind is recognizing patterns, so if you utilize and are consistent in your use of patterns it becomes much cleaner and, if you are able to understand where thing goes, you can also find it again making it a self reinforcing process clear divisions if you have a lot of code in the same grouping e.g. services, it can become unwieldy it is therefore a good idea to divide it into small groups with clear divisions, if the divisions aren't clear you wont be following a pattern for long e.g. separate business logic from facade services standards following standards makes it way easier for next (or just you in 6 months) the person to read your code as it provides an overall familiarity and allows for new concepts to better be implemented now if you do end up having to deal with the remnant of an old hermit programmer who refused to explain how anything worked except in a dead language where you aren't allowed to rewrite it because it "would take up to much time", then I'm sorry there isn't much you can do other than try and follow the existing flow so you can fix or add the logic you need, this includes a ton of breakpoints (conditional breakpoints can be very handy), sometimes using console logs because things get weird, the key thing is to take a deep breath when you get frustrated and take it slow


I_Boomer

Ideally, following comments is the best way as it would save a lot of time. But that was then. They used to have people cross checking your work and challenging you on it's functionality. A lot of that engineering-like behavior has gone by the wayside due to wage capitalization so it has evolved that the real truth is obtained by reading the code, and like the other folks said, debugging with different cases is the best way to see what is happening under the hood.


ajdude711

Dealing with large codebases could be tough. Give it 3 months just looking at it and then you will be reading it like language.


Shanteva

Assuming they have some unit tests, start by reading those. Tests tend to have less spaghetti and _should_ document what the tested class does. Try to focus on the topology of interfaces before worrying about down in the weeds implementation


Shanteva

Also don't take interface too literally. I mean the public methods whether they're an IInterface or a single class implementation


Strenue

This


commentaddict

You can always attempt to write tests that should be there.


Frown1044

Pick up simple issues and figure it out. Don’t write any code until you find good examples of similar solutions.


AdmiralPelleon

I read the documentation for 3 minutes. Then give up and re-write it all myself.


homeownur

Easy. Concat all files and then sort all lines alphabetically and by length. Result reads very nicely.


presdk

Start with getting very familiar with the syntax by routinely reading and writing heaps of code (good or bad). Once the language comes to you as second nature it’ll be much easier to reason with the given codebase and dissecting it into groups of behaviour quickly, intentionally knowing that the implementation details and assumptions haven’t been fully explored yet. It also helps to gather contextual information about the application which will give you leads on what you might expect. For example, if it’s a web application for an ecommerce store you will expect input validations, interactions with the database for CRUD wrt. business logic surrounding a shopping cart (e.g empty inventory, lack of credit, credit credentials incorrect, etc), error handling, further forwarding of the action to another app via a queue, etc. If there are framework or library specific code that slow you down it would pay to read the docs and learn it upfront to save the context switching.


MollitiaAtqui310

One tactic I use is to identify the 'hub' classes/interfaces that everything revolves around, then work outward from there. Also, don't be afraid to temporarily refactor confusing code into something more readable - it's a great way to understand the author's intent.


gameplayer55055

ASP.NET is understandable, thanks to MVC architecture. But other projects are a bloody mess. Still better than JavaScript and c++ codebases that totally suck


Aggressive_Ad_5454

I start with VS and Resharper. I then use IDE code nav tools to look around, starting with whatever I need to work on. Show Definition, Show References. If classes, methods, and important props I need to work with don't have comments, I create a branch and add them. Because the IDE uses them. Project-wide search is also a big part of my detective / reverse engineer toolkit. Sometimes I comment out a using statement to see what turns red.


SergeiGolos

There are a lot of good answers already listed in the comments. Many of them are explained in much more detail in this really cool book I am currently reading [The Programmers Brain](https://amzn.to/3wbLyhB), which diggs into the cognitive science to explain what is going on on our heads when we read and write code, and the strategies we can apply to get ready better at it.


trebblecleftlip5000

This is such a weird question. By "complex" do you mean "poorly written"? In that case, start refactoring until it is no longer "complex".


branster464

Lots of good suggestions here. I would also add getting familiar with navigation hotkeys: F12 - go to definition, Shift+F12 - find usages, Ctrl+F12 - go to implementation(s), Alt+F12 - open definition preview window (This one can be chained multiple times in a single preview which is grwat for not bouncing around too much, but kind of loses some usefulness when definitions aren't concrete)


cccuriousmonkey

Walk through 1-2 main use cases using debugger. Having consistent structure of solutions helps.


ThrockRuddygore

It may sound dumb but an AI like Phind can do a great job of explaining what code is doing.


LanguageLoose157

Man, we winform C# code base, i still don't understand what the F it is. Way to much coupling going on. Test are a nightmare. Whereas our Java code base is so much easier to understand. I guess ASP.NET code base should be easier to understand the code flow vs client facing app?


BranchLatter4294

Just get GitHub CoPilot to explain it to you.


Kilazur

I'm pretty sure I could crash it with my legacy code


ahatch1490

I have crashed it with a legacy project. Had to turn it off whenever I work on that project.


Aspirations84

I agree with what most everyone here is saying about debugging and stepping and I use Debug.WriteLine() as well for lots of things. The only thing I didn't really see mentioned was using the IDE tools like in Visual Studio and VS code I can just follow the references with F12 and/or References from the context menu along with F1 to the documentation for .NET classes. Granted that takes some good understanding of stuff like dependency injection, inheritance, and polymorphism, but all those are really important to know too and just reading the code of complex code bases is a good way to learn that.