DEV.BG .NETworking Conference 2025

Непопулярни мнения за .NET програмирането

32:40 · 09 Apr 2025 · YouTube

About this talk

This talk focuses on best practices in programming, particularly within the context of C# and ASP.NET projects. The speaker discusses the usage of the 'var' keyword and its implications for type safety, emphasizing the need for clear type definitions to avoid potential runtime errors. Additionally, he addresses the disposable pattern and the importance of proper resource management in C# applications, advocating for dependency injection as a solution. The speaker also shares insights on database design, asserting the significance of data integrity through effective schema management. He discusses the evolution in syntax and programming conventions, including the adoption of records and modern coding practices like DTO usage. Ultimately, the talk advocates for a critical approach to coding standards and a balanced perspective on technical decisions in software development.

Full transcript

[music] The purpose of this presentation is to at least once tell yourself, "This guy is a complete idiot, what nonsense he's talking about," and at least once tell yourself, "Maybe he 's right about that." I have to try. I am sure that we will achieve the first goal . My professional experience is as a programmer, mostly medium-sized ASPNet projects. And the first project I worked on was

porting from DNET11 to DNET 2.0. And the biggest project I've worked on had 21 programmers working on it at the same time, and of course a lot of other people. And I know that there are people here who work on much larger projects, people with companies with hundreds of programmers on one project. And no, the practices I'm suggesting here may not be applicable there. And in general,

the practices I suggest may not be applicable. Well, in general, we should have a critical attitude towards things. And for me, I host the podcast NS to Nъs, which is the oldest functioning Bulgarian podcast and we make one episode a year so that we can still claim to be functioning, but the others don't even do that much. That's right. Practice one. Declare a class called lime so

that your colleagues cannot use lime. And this has been going on since 2008, when they introduced VAR, it was necessary because VAR is new, a new keyword, in order to maintain backward compatibility, they did this. Then they look at these things very strictly. That's not the case anymore. And that's why when you have a class called VAR the keyword of War is turned off. Of course, don't

do that. And this presentation is not about perversions with C#ARP. I really love these presentations, but unfortunately you'll be watching other things, right, things that I really do. Ah, but still the debate about VAR, and these are the three ways we can do VAR. Oh, so there were three so far. And one thing is to always whitewash, the other is to never write anything. The problem with

whitewashing is that sometimes type matters, and especially the classic example of a type that matters is iquiable or numerable. In one case, if you don't assign the wrong type to the same code, but with a link, you can query the database or get the entire table from the database and filter it in memory, right, which is obviously not what he wants. Everything above on your test server

or on your machine can work because you don't have any data in the database and then on production everything can crash. And I've actually seen a bug, because I did n't see a bug, but I saw a person sitting there wondering why something wasn't working for him because he had assimilated the wrong type for lime. I've seen it in practice. True, as a junior programmer. And so.

That's why I'm not a fan of VAR. I was a fan of the third, the third way to use it when the type is obvious. However, there are disputes when the type is obvious. And for example, Microsoft thinks that the latter type is obvious because it's written from a sheet, but a sheet of what? Microsoft thinks I don't care about that. Oh, I also had a disagreement

with them and wrote them a letter and so on. And so. The debate about Vare Bloody. This is a rediIT for C#ARP and you see on a typical day there is at most one with 85 comments. Even that is unusually high. And then, every six months or so, someone new pops up and asks if they should use VAR, and you see what happens. 400 comments, 240 comments,

173 comments, right? Everyone has an opinion about this. It's very hot, isn't it? The discussion, by the way, reaches some incredibly technical levels, right, in the sense that some very smart and competent people are arguing about this nonsense. And the lesson from this is that someone has to pound the table, become a dictator and introduce fascism into the code and say this is how it will be.

He may explain, he may not explain. The point is to say so it will be. The point is to end the arguments, because the most insignificant things can be argued about the most, right? And because everyone has an opinion there, if it's about some complicated matter, fewer people have an opinion. And so are the tools of the dictator, the tools of fascism in code. First of all,

the editor, I guess you all already know about this thing, where we define what things should and should n't be used, like style, formatting, and so on. What fewer people know and maybe something useful that someone might learn from this lecture, a tool do netformat, which you can run on an entire solution and it will format the entire solution according to the rules given above. This way,

for example, you can change a convention or impose a new convention that was previously written differently. And also, a format can be integrated into C and you can make it raise errors, for example, if a rule is not followed. So a perfect tool for dictatorship. And so my decree actually on the issue of VAR, I used to be in the smallest group, but a new way of

writing it appeared and even fewer people were in that group and I, of course, had to move. And this is to use target Type new. And this is the result is just like lime when the type is visible. But we don't argue when the guy is visible. If you can write, right, then the type is visible. If you can't, like the example below with the query, then

you obviously have to write the type, which suited me perfectly . The good thing is that it is also consistent with how we can assign fields and properties. And so, but that, as I said, doesn't matter. The important thing is to have a dictator. And when I said dictator, that's how I imagine dictatorship in my project. And my boss is a bit bossy there, but in the

project he listens to me about everything and even calms down some of the more controversial requirements . That's right. A and A see what book candidate dictator Vinnethu is reading, Volume Two. Yes. And the reality, of course, is different. In the picture you can also see why NS NS has one episode per year. And the technical term for this is Feature creep. Oh, okay. The next thing,

this is an outdated thing, ah, but so that those of us older than us can remember, ah, implementation of disposable, of the disposable pattern. What do you think is wrong with this code? We don't have time to think now. And as in a normal project, we don't have time to think. And the purpose of the disposable pattern, in principle, of this whole circus, is to be able

to dispose of something that has an unmanaged resource with it, if we forget to use it, and for the garbage collector to use it. How is it? This is the one with the tilde in front called er. When the garbage collector collects the object, if it sees that the object has a er, it sends it to something called finalization Q and there, at some point, another thread

will collect it. The problem with however we need to implement normal. We cannot rely on this because the Garbage collector is only interested in memory prure. It may not run if you run out of sockets, for example, or there may be situations where the user closes the file in the application, but cannot delete it from the hard drive because it tells them that the application is still

using it because someone has not called dispose. And this is the purpose of the disposable pattern. We have to call it, but the purpose of the pattern is to have a fail safe operation, at least for you sometimes. And, of course, this is wrong here and the right way is like this. Why is this the right way? I've written it the wrong way many times. I wrote

these decrees about this disposable thing, but it actually makes no sense. And why doesn't it make sense? Because the TCP client is no longer an unmanageable resource. A TCP client is something that has an unmanageable resource in it. And the correct way to implement the pattern in this case would be like this. And when we open the TCP client and find the socket in it and when

we open the socket and go to its disp method, we see this and this. Hey now this is a disp method, not like your nonsense. And this. And finally we put six closing brackets and two empty catch blocks, right? Just as it should be done. Don't do like the Confucian masters. You might get hurt. Yes. And why doesn't this error happen anymore? First the instruments started to

catch her. First, now Visual Studio, when you install it Out of the Box, catches this if you do something like that. But more importantly, we started using depend injection en masse. And the rule for disposes the object, and it disposes. That's the convention. And accordingly, unlike the example where the object was created in the constructor, when we use dependency injection, we inject it in the constructor. That

is, depend and created it, depend and injection will destroy it. However, still check if the objects are deserialized, otherwise it will not destroy them. Also for this convention, for example, when we use the stream reader, stream writer functions, you may notice that they often have a parameter whether to use the underlying stream. That's why there's this parameter, because the convention is not to, and it actually becomes

quite problematic for some classes that decide to type underline stream, and you don't want to, right? There are some like that, but most cultural ones have an argument for this, but that's in the past. Let's see about one eternal thing, right, the present and past for the base. So in my experience, data is the most important thing. So we've rewritten, we've thrown out entire projects, but the

person wants the data, the client. And in general, when I hear it, especially in various places where it talks about DDD, and when I hear The database implementation detail, my hands just start shaking and I try to hit the button on the tab, to close it. I don't want to hear any more, okay? This is because I can treat the base with disrespect, which is a total

contradiction of all my experience. And once, a colleague spent two weeks writing a program to migrate from an old database to a new database for a project that we were rewriting because the client said we couldn't throw away the data. And that project came to us because the other company had really screwed it up and we agreed to throw it away. The data shouldn't be migrated and

their database was very bad. And that's why Yes. And here some smart person on the internet, who wrote an article, wrote this thing. Because that's just where the culture comes from. That's why I'm against Cold First. We don't let an anti-framer write migrations and so on for me , because I just insist on seeing and knowing how things are arranged database. That's right. And now as I'm

telling you how everything in the base should be fine, the next example is how careless I am with the base. And when in a base that I don't have a type or even when I have a type, it's almost equivalent. And what is the right thing to do? We make tables that contain the value of the NMA and an example name. That's not even important. And to

the columns that do and the columns that don't contain that, to make sure that no invalid values ​​will be entered there. And that's the right way. However, there is one big annoyance. And when we add some new value to a in the code, we may not be required to touch the database at all. It might be absolutely fine to add the new value, but the problem is

that when we do it that way, we have to touch the database, we have to write migrations and tedious work, instead of just writing our code and forgetting about everything. And now my experience is that this thing has never, ever in my experience caught a bug, except for one very specific kind of bug. And this is a bug with the default value of nama, which is zero.

And now I'm thinking, I'm going to make the numbers, even when they have a value like N, always start at one and I'm going to put a constraint in the database that this thing should be from one to 1000, for example, and that means zero can't enter the database. At the same time, this doesn't protect me from completely invalid values, but in practice I've never seen a

bug like this before, and as I said before, I've never seen a bug like this that was stopped by the Ring. And this significantly simplifies my database in terms of the number of tables. It's not that it's any kind of complexity , but it fills up the screens and makes me not write migrations when I enter a new value for the NMA. And so. Natural key. So

I've studied and I don't use natural keys. You know the classic example here. It may not be unique, it may change, and so on . Do not use natural keys for keys in the database. Well, yes, but at one point I put some languages ​​in the database and I put some IDs and I made a table and at one point I realized that it was super confusing

whether I wrote BG tirebg or, for example, 12 and so on and they were all over the code, one couldn't understand where the code came from, where it came from with a and I came to the conclusion that I can never, but I can't believe that the code will ever be changed to Bulgarian and it won't be a bug, right? And this is a natural key, which

in my opinion will change. So I'll use it next time . So, now how do we write types? The old way, ah, which we all liked for years, of course, without target type new, ah, but this is the object initializer syntax, where we just assign ourselves properties, write ourselves curly brackets, row, properties, equals and so And so. Here we have a small problem that H already has

a value, well, it doesn't Now, what about when we used to write the old way, when we weren't too lazy? Well, we were making a construction set. But I sometimes did that to myself in some important classes with logic in them. But it's obvious that we were lazy, because every name is repeated four times and every type twice. And in principle, this has always been the right

way. The constructor must be an entry point. There, it potentially has to go through validation and so on. And over the years, all of us in the DNET community have developed an addiction and are addicted to this syntax, this old initializer syntax. So, the new thing we have is recs. And we see that it saves us a lot of writing and we get the same effect with

the constructor. If we want to add some real logic in the future , we can actually write this constructor, and so on. These are especially useful for the most common type of class. This is the DTO. At most we have DTOs. And we receive other benefits as well. I assume you've looked at rec value semantic and so Oh well. And now the complaints begin, because people are

addicted to the old way and start to grumble. Why? Why shouldn't I change? Why do I want the old one? Well, calling the constructor on one line was ugly. Well, call him out on many lines. But it's clearer with the property names. Well, okay, write your names. You even have a way, if you have well-named variables, you can use them, right? And in other places where you

need to use the name, you can write it. Unlike the object analyzer, where you write everything. So for DTOs, some people don't know that validation attributes work, and even when an attribute isn't made to work on an argument, we have this syntax with properties that you can see, which actually sends the attribute on the property. And Eon Musk told me to validate my age until I was

120, then they wouldn't pay pensions. Of course, the age should be, you know, a date, not a date of birth, but that's it And one of the problems with using DTOs is that when you have a data form in Blazer, it doesn't work with records. I will write to them Isho. So, the even newer and wrong way. And because people are addicted to the object initializer and

insist and grumble at Microsoft to make it work properly and start some Init. This is like a set, only read only set, only it doesn't work only in the declaration and in the constructor, but what if you also use the object initializer syntax. Yes. Required is another. This is to require it from you, just as the constructor requires you to give it the argument. This is required

and object initializer synx requires it. So, in order not to write constructors, they put 15 keyyards. This is so much simpler than a constructor. I tried using required once. NT framework does not work with records. It relies heavily on mutability. Theoretically, an ORM could be made that uses the with mutation and manages to work like an anti-franx. Notifrank was before there were records and the work was

clear. I tried using required there. I'm shouting, right, come on, at least some place, and I realized that I very often construct it in steps. I get some of the data from the DTO, some of the data from some computer, and some I just want to retrieve from a server , right? And that required thing really bothered me. Oh, and in the end I found out that

it doesn't work and I still haven't found where to use it. And keep in mind that if you need to put a temporary dummy value, it's better not to have required at all. It's better to put an exclamation point, at least so it's visible so I know it's wrong, So, a few problem details. So it's a project with funds I've worked with. They are always like that,

some object has arisen by itself, which is a standardized internal error, where we know that it has such properties and so on. If I return a status code above 400, for example, you then know that there are such and such properties in the frontend and the error is processed and something is done and they thought of standardizing it nicely. As for the first version, I don't know

what the point of a standard is where absolutely everything is optional. So I'm not really sure it makes sense, but okay. A big improvement in 2023. They made Type, yes, it's a must. Now why Type? Type is really the most important thing. And type is what our if switch should be based on. Then it's like the type of the special. And type is the most important thing

in a thing. And the problem is that ASPNET used to set it by default and ASPNet sets a type that corresponds one-to-one to the status code. And the problem with that is that we don't actually want everything that's a 403 or a 409 or whatever it is. Okay, maybe a 404, but we don't want the rest of them to have the same type because we're handling some

kind of error. We are supposed to check the type and then these things underneath, which are called extension properties, which are whatever you put in, that is, to know that when the type is so and so , then these properties are there and we can work with them. That's the idea. People very often skip the type and start using other things, like the title, right, for something

like that. go ahead and put your types everywhere. In general, the type is supposed to be a URL, and if you're really pedantic, you can even put a description of the error on the corresponding URL, but that 's not mandatory, right? The point is that your different errors should have errors that are handled in the same way, that they should have the same type, and no one

can convince me that all 403s and all 409s are handled in the same way. And so. The next thing, and my thesis is, readable code reads like an English sentence, and that's where all our conventions come from, what's a noun, what's a verb, and so on. When we follow them, the code naturally runs, of course, imperfectly like an English sentence. And we also need the mathematical operators

that we learn from young children, greater than, less than, plus, right, and so on. And, of course, let them sit in the code. We have internalized them just as we have internalized language. And this opinion is not actually unpopular, but it has consequences. For example, the consequence that we need to check for nl and we need to use the quy comprehension syntax for link, which is like

sentences, not with some unclear arrows there. Unfortunately, the syntax is neglected, and some things are required, like putting parentheses around everything and adding some methods. For example, I don't know what's been stopping them for so many years from adding distстиct take and skip keywords, which would help with paging, for example. And yes, I can't defend this thesis very easily . I wish this syntax worked everything. There

are also super features with it, like an LED that does things. And so. And this is e. Let's not use fields. People here also react very defensively. And what am I doing? I'm just using private properties. You see, it's usually not the code that's longer, but it's the concepts in the code that are reduced. And how did I get to this? One time I changed it, I

was doing some OP design and I changed whether there should be inheritance, whether there should be a property or not, sometimes it would be prived, sometimes I would change it to field, sometimes I would change it to something like that and I decided that whatever, why am I using fields when I can use properties anyway. And I now admit, I admit, that these are not very strong

arguments in defense of prortis. Fact. That's not a very strong argument. However, for fields, there is absolutely no reason, absolutely no reason to use Fields and then performance. If GTA can't handle this, just change the language if you need it and your GTA can't handle this. But wait, what is this field that is not in the private property, right? And so I have thrown them out of

my codebase. And there is one thing that is only supported on fields and that is ref fields, which are used in refstracts. And refstracts were maybe, I'm not even sure anymore. This allows a compiler to sit in the shell where what was taken from the field was used. some things like that. That is if you want to book a spa. I haven't written such a class yet,

but if I do, I'll embrace it and use fields. And another thing, after I started this practice, Microsoft decided to screw me over and made Primary constructors. And I have many objections to them. One is that I can't do my own thing. And I have other objections to Primary Constructors, and since I'm a dictator, I solved the problem radically. in Editor Config. That's right. And I also

don't use Vertical Slices or Feature Folders. That's a very nice idea. The first time I saw it, I even recorded an episode of Nar about it and had a lot of fun and then I tried to do it. And so I sit there wondering how to put my business logic in assemblies and I go and ask on some forums and you won't put your business logic in

assemblies. That was the answer, and the data, the DTO, and the business service, everything there, the endpoints, in one folder, and I didn't like that. The layers were just some kind of convention , right? They had no real meaning. They did n't exactly believe in layers. We'll do them in the feature, but that's it . And now there are some layers that it is inevitable to believe.

For example, many people find it difficult not to trust SQL Server, right? It's inevitable there. But I believe very strongly in the business layer. And I do n't believe that. My requests are between the business layer. I disagree that queries are not business logic. Requests are business logic. The one that came from the client, filter by this, filter by that, that, it doesn't show. The man shoves

him into the washroom and shouts, "This isn't a business meeting, is it?" So I quite calmly mix my requests with the other business class. But I very strongly believe in the separation between the business layer and the web layer, the one that deals with HTP. Oh, here's a story, but there's no time for it. No, I really measured it and there's no time. And the other thing

I believe in is not a layer, but that DTOs should be in separate assemblies. I didn't believe this before, but then Blair came out and I really started to believe it. Oh, because it makes a lot of sense to share DTOs from the server and the client, and between the Blazer client server. That's right. And business benefits that I have seen over the years. I have migrated

projects from MVC to Webap and to Spa, right? And from DNET framework to DNET core. While I kept the business layer, the top layer of the old project was developed in parallel , until we were able to completely replace it. And both are being worked on, which use the same business layer. And that worked. If you're running a business, it won't be literally 100% the same, but

you can work on two projects in one code base, with a new web browser on top. And I've seen that too, right, in a sense, I've done it, and I can imagine other things that I haven't seen, for example, migrating from webs to Blazer or something like that And these migrations would in some cases be rejected by the business because they wouldn't be migrations when written, right?

And so for me the business layer is something that should be there. And now I've caught the fans of Feature Folders saying that they don't use Feature Folders either, and I ask them where are your React components? I'm not here. That's different. It's one thing, it's another technology. So it occurred to me that maybe they were lying to me. And so, according to them, some technical layers

are important. Just not the ones that are important to me, right? Mine aren't important, theirs are. If they were, then if someone talks to you until they show you how they set up all the processes there, so that their react components are in the folders, right, and the build process goes there to find them among the C#ARP files and compile what they need, then they don't believe

in Feature Folders either. And so. I still use Feature Folders, but after separating the layers, for example, this is a screenshot of one of my projects, in which this is the Blazer Web Assembly part of the project. And in what it's called, it says service, right, for example, AN Service, let's say. And this is a rapper of communication. This is where I turn 404 into N, some

things into an exception, some things into some result type. Oh, and when I was doing it, I thought to myself, "Well, this is an ideal case for Furefo FSE." That is, once you define the layers on which you will have a technical boundary, a hard technical boundary, that is, a DNET assembly, right, then it really becomes convenient to have and use Feature Folders internally, and this thing

is really nice. And this is Viktor Chernomyrdin. And he is the first Prime Minister of the Russian Federation. Ah, the creator of Grasprom, you know, Piel, and also the author of Chernomyrdinki. Chernomyrdin's jokes are the kind of things that make you wonder at first if he's stupid, if he's drunk, what he's doing, and then after a while you start to wonder if this isn't actually something very

wise that he said . And the Bulgarian equivalent might be Ivan Slavkov, brother Chernomyrdin is better by the way, but something like that. Now I don't think Viktor Chernomyrdin has ever been involved in software development. We doubt it, but he still said some of the wisest things about software development. We tried to do it the way it happened, but it happened as always and now I think

this original statement is about the Russian transition to democracy. So just imagine, if we don't try, then that doesn't mean we don't try to do it right, if when we try to do it right it happens as always, imagine if we don't try what will happen. yet, software development, right, I believe that maybe every next project, it will generally be the same as always, but maybe it

will be a little better. After all, we only make software. Our problem is not as difficult as the Russian transition to democracy, right, and that's the end of it. And in fact, there was time for the story. Come on, come on. Come on, bye.