Transcription
You wrote code in 1C, and then you couldn't understand it yourself. You received a remark about violating standards, but didn't understand where the violation was. You don't want to look like an eternal junior. You want to write clean, correct code. Then this video is for you. Today I have selected 20 1C rules and standards that seem very important to me. I encountered such violations every day in my practice. Today we will analyze, for example, how to correctly name functions, why a transaction is needed, why you can't just wrap everything in an exception handler, and why sorting in a value table can kill all performance? All links to standards and other materials from the video, as usual, will be available via the link in the description on my channel. So, let's go. One more tip before the video starts, how to make senior developers accept you into their, so to speak, inner circle clan. How to use this video? So that it brings you the maximum benefit. Firstly, watch it to the end to understand, in general, what is in this video and in what sequence. Next, try to apply these standards in practice, but not chaotically, but develop a habit. For example, dedicate one day to the first standard, the second day to the second, and so on. And after 20 days, start again with the first. After some time, you will practically apply these standards on autopilot without thinking, and all the seniors will surely respect you. Let's start with standard 647. It is called names of procedures and functions. And the names of procedures and functions are the first thing you see when you look at the code. If the name speaks for itself, then it is many times easier to read and understand. What is written there, a good function or procedure name should clearly reflect what it does, and not make you guess or delve into its internals. If you can't come up with a clear name, then most likely, the procedure itself was initially written crookedly and architecturally incorrectly. If the name comes to mind immediately, it means the architecture is fine. Now let's go through the rules a bit. Firstly, names should be, so to speak, descriptive. For example, don't write something like execute check with parameters. Parameter one, parameter two, parameter three. This tells nothing to someone who sees it for the first time. It's better to write something like, name the object's attribute of a given type or fill in the attribute names by economic operation. From such a name, it will be immediately clear what this function does, what its parameters are. Secondly, we write names together like variable names, that is, according to the same rules. Each word with a capital letter, even prepositions and pronouns of one letter, also with a capital letter. Also, you don't need to specify the return type of the value in the name. For example, it will be incorrect to name it get_array_of_roles_with_add_permission. It's better to just name it roles_with_add_permission. The return type of the value should be clear from the context, and as an exception, if it's completely unclear what the function returns without it. And we name procedures from the infinitive form of the verb. Load_counterparty is correct, and loading_counterparty is incorrect. This is an action. Therefore, we need a verb. According to the rules of the Russian language, recall the infinitive form of the verb. And functions are a bit different. They are usually named because of what they return. For example, it will be incorrect to say get_full_user_name. Well, this is redundant, and it's correct to name it simpler. Full_user_name. And if the function creates a new object, then add the word new. For example, new_form_field or new_catalog_item. If the function checks some condition, then start the name with the word is. As if hinting that a boolean will be returned. Or use a participle. It will be incorrect to write check_document_posted. It is correct to write document_posted or is_external_task. If it is important how the result is obtained, then you can use a verb in the function name. For example, select_data_by_rule or transform_data_by_rule. Also, if the function simply performs an action and the result is not important to us at all, then name it the same as a procedure, from a verb. For example, allow_editing_object_attributes. Everything is simple here. A verb, when it's an action, a noun, when we return a value. And no mysterious abbreviations, as beginners programmers like.
The next standard is 453. Description of procedures and functions. To put it briefly, procedures and functions need to be described, to give comments there before the procedure or function starts. But this is not always and everywhere necessary. It all depends on how complex they are, and it depends on who will use them. When it is definitely necessary to write comments according to the standard. If a procedure or function is exported, meaning it will be called from other modules or even from other applications, then a comment is mandatory, because someone else will read your code, and they should understand what the function does without long разбирательств in its body. For other cases, look at the situation. If the behavior of this function is not obvious, some non-standard logic, it is very large, or it needs to be explained, because something is done in a certain way and somehow done cleverly, then a comment is definitely needed. If everything is clear from the names of the parameters, then writing additional text is not mandatory. It is also important not to write meaningless comments. For example, don't sign that the procedure is an event handler for opening. Well, damn, that's clear from the name. When you do write comments, when they are needed, you need to do it competently. You need to start with a verb, describe what the function does, for example, creates a structure, blah-blah-blah, or checks availability, blah-blah-blah. You don't need to duplicate the name of the function itself, it's just a waste of time, it's empty words. If the function has parameters, then you must specify their types. This is a requirement of the standard. Types are written separated by commas after the explanation. If the parameter is complex, you can give a link to the function that creates this parameter. Or as an example, how it should look. For arrays, it is desirable to indicate what they consist of. For example, an array of strings or an array of references to tasks. If the function returns something, you need to write what exactly and of what type. Sometimes one word is enough, for example, string. If the return can be different, then write each option line by line and what it means. You can add an example of calling the function - this is good practice, especially if the input parameters are complex or not obvious. And if there are many parameters or they can be of different types, then it is useful to add a section with call examples. That is, examples like this set does this, this set does that. This is good practice. It saves other programmers their time. It will also save your time when you return to this task a year later and try to understand what you wrote here. If the function becomes obsolete at some point, then you should write obsolete. Use instead of it, this, in this, in this module. You can also see this in BSP. There is often such a comment that it is obsolete, use this, this instead of this function. Why are functions and procedures not deleted from BSP immediately? Because developers might use them somewhere. in many places, and to avoid forcing them all to rewrite, this function remains. But it is written there that it is obsolete, please do not use it anymore, and someday, most likely, this function will disappear. Also, if there is a compilation directive in the procedure, then write the comment first, and then the directive. This will be more logical to read. Here, in the example on the screen, you can see that the comment is written first, and then the directive follows. By the way, if you are too lazy to write comments for functions and format them all in common modules, then use artificial intelligence. I will leave the link on my channel.
The next standard is 640 - parameters of procedures and functions. Parameters are what other developers will work with from the outside. Take care of them. And if there is a mess with these parameters, then the whole function becomes completely incomprehensible and inconvenient. Firstly, give parameters clear names. Don't write P1, P2, or rec_req. It's better to write something like start_date, document, object, price_type. It will be clear from the meaning and from the subject area, so that the developer, looking at these parameters, even without looking at the function body, will immediately understand at least approximately why this parameter is needed. Secondly, this parameter will be used further in the function body. And if you name it, for example, P1, P2, it will be inconvenient to work with it. It is too short or too unclear. Secondly, do not replace parameters with module variables or form attributes. This is a dead end. This is a path to the wilderness, bugs, and spaghetti. Everything that can be changed and used externally must be explicitly passed in the parameters. Next, the order of parameters. First, we write general ones, and then specific ones. That is, for example, first the object with which the function works, then the name of the field that, for example, we will change, then the flag with which we will work, includes VAT or not, or something similar. But not the other way around. First, larger objects, more important ones. Don't stuff a bunch of parameters into a function. If there are more than seven, then according to the standard, they need to be put into a structure, put into one parameter, which will be a structure, because otherwise it will be difficult to read and use. If there are more than three optional parameters, this is also bad. You need to group all this into a structure or break down one large function into several small ones, which will have fewer parameters. You can also see all this in BSP or in standard configurations. This approach is used there. What else is convenient about using a structure as parameters? If we need to add new parameters, then when using a structure, it is much easier. We just add as many new parameters as we need without changing the structure of the function itself.
469. Rules for creating common modules. Business logic is recommended to be placed in common modules, not in form event handlers. You have probably heard this often, that there is a form, so you don't need to implement any global logic specifically in the business process part, but you need to move everything to common modules and implement everything there. And the form module is intended specifically for working more with form elements and so on, with form events. And all business logic should be implemented somewhere outside in common modules, or at least in handlers. Where did this come from? I didn't find it explicitly in the standard, but everyone always says and advises this. There is an indirect reference in this standard. What does it say? Server common modules are intended for placing server procedures and functions inaccessible for use from client code. They implement, pay attention, the internal server business logic of the application. This phrase tells us that we should implement all business logic there. Thus, to summarize. A common module is not a trash can where you throw everything in a row, that seems superfluous somewhere in the form and so on. It is a logically structured place where procedures and functions are stored that solve a specific task. I have just briefly told you what this standard is about. That is, with common modules, we create a prototype of business logic architecture. Therefore, this is very important. It would seem, well, I created a common module, wrote some procedures there, and that's great, we call them from outside, everything will be fine. But if we are doing some serious modification, then we need to initially think about what common modules we will have, how they will interact with each other, and what exactly we will store there. Think about the business logic in advance and only then will we write the code. The creation and configuration of common modules are best entrusted to and coordinated with your architect, who is the most important person for the configuration. A little more about how to name these modules. That is, the name should correspond to the meaning. If the module processes files, we call it file_processing. If it logs errors, error_logging, and so on. You don't need to write functions for working with files, or procedures for working with files in the name. It's already clear that there will be procedures and functions inside. You need to focus on the essence of what will be inside the module. Also, you may notice that some common modules in BSP are marked with suffixes. global, full_rights, repeat_use, redefinable. There is also a localization suffix. You will figure this out yourself. Let's summarize, that a common module is not just a piece of code, it is an architectural unit, as I said at the very beginning, you need to think of it as part of a large construction, where everything should be clean, understandable, and for its intended purpose. One module - one task. There are clear rights, a clear name, and no mess.
Next, expression переноса (line breaks) 44. It would seem, what's the big deal with breaking an expression? Break it however you want. Well, I've seen so many wild break styles in my practice that I now understand why this standard was developed. The main idea is that if a line of code becomes longer than 120 characters, then break it. Even if you have a large monitor, the code will be readable and understandable on any resolution, any monitor. Because if you have a fifty-inch monitor, it's not a fact that other developers will have the same, and it's not a fact that you will have the same monitor tomorrow. Well, and again, moving your head left and right like this is not very convenient. Then, as they say, your neck will hurt. If you have a long formula with arithmetic, you need to break it. Plus and minus signs are placed at the beginning of a new line, not at the end. This is important, because many people get used to putting them at the end and work like that for years, but according to the standard, they should be placed at the beginning. When concatenating strings, they also need to be broken. It's better to put the plus sign at the beginning of each line. Well, if it's inconvenient to read like this, then you can do the opposite. The standard says that the main thing is that it reads normally in this case. If many parameters are passed to a procedure, each parameter is written on a new line or aligned by indentation. The parenthesis and semicolon are placed on the same line as the last parameter. This is also important, because everyone writes as they are used to, so you can write with inconsistencies, especially beginner programmers. Next, if you write a long condition in an if section, you need to break it down into parts. Each logical block on a new line, and logical operators OR and AND, respectively, like pluses, also at the beginning of the line. This will make it easier to perceive the logic with your eyes, and because, accordingly, the standard is in BSP. We consider BSP as the benchmark. If we write a long line in a query or in text, then you don't need to stuff everything into one line, you need to break it. And if the line is then shown to the user, then it's better to leave it as a single line so that there are no breaks. Remember that this is not for the compiler, not for some abstract standards. This is for you personally and your colleagues, in the end, so that you yourself can read your code a month later quickly and without pain.
440 - is the use of duplicate code. This is my favorite. constantly, constantly, even if a developer has worked for 5 years, they still have the urge, like some developers, to copy and paste, as they say. Why not? So, what does the standard say? When you copy a piece of changes, it's called duplication. It has disadvantages, who would have thought, right? Firstly, you copy not only the logic, but also the errors. If there were bugs in the original, then they will now be in two places or in as many places as you make copies. Now, if you want to fix something, it's good if you remember that this piece of code is used in many other places. But usually everything happens in a chaotic mode, of course, you fixed it in one place, as indicated in the task, as on the Kanban board, you fixed it, and you rarely remember about the other pieces of code. This is a big disadvantage of code duplication. If it needs to be fixed, it's easy to forget where among these ten copy-pastes you've accumulated it, and you will definitely forget. Therefore, if you encounter duplicate code, get rid of it as soon as possible. A task to fix a bug came in. You found it, fixed it, left happy. It turned out that the previous developer, for example, well, maybe not you yourself, but the previous developer could make 10 copies of the function. It also happens that he did a copy-paste. Well, in each of these functions, either he changed the logic a little bit himself along the way, or other developers gradually added their own according to the tasks on the Kanban board, and now you will have to find all these places, understand what has changed there 10 times, and fix it everywhere in ten places. And instead of doing it once, you will do it 10 times. Bam, attention, drum roll. you will thus introduce additional errors because here you fixed the fixed logic, here you misunderstood the algorithm that was changed by that developer. And please, here are new errors. And again, all this needs to be, well, ideally, passed for testing. In general, think carefully. I hope I scared you with this information. But according to the standard, code duplication is allowed. In some cases, for example, if you understand that these two fragments will differ significantly in the future, meaning they will follow different logic, then yes, it is reasonable to temporarily copy them, and then develop them independently.
Standard 438. Checking for an empty query result. So, when you execute a query in 1C, it's important to quickly and correctly understand whether there is data in it or not. That is, there are tasks when you need to figure it out immediately. There is data, do this. No data, do that. It's simple. If you don't need the data itself, but only need to know if the result is empty or not, then use the method result_is_empty. Why is this important? Because if you immediately start iterating through the selection, even if you just call the next word, the system already starts loading data from the selection. And this leads to unnecessary server time costs. And if you just want to check for the presence of rows, that is, if the query is empty or not, then this is an unnecessary load. Correct. So, execute the query and immediately check if it's empty. If it's not empty, then there are rows. But if you then need to actually process the query result, meaning you plan to export it or iterate through it or do something with it, then you shouldn't call empty at all. Just take the selection and go into the loop. Calling empty here will be superfluous. It will only create an unnecessary load. Although, of course, it's a small load, but still a superfluous call. This standard is quite interesting, although short. I encountered in some chats, forums, that you should always check for an empty query. Allegedly, the standard dictates so. But in reality, here is the standard I described, I read it to you. There is no such rule directly.
Standard 758. Aliases for data sources in queries. What's important here? The main thing is that an alias is not a technical formality. It should convey the meaning of what kind of table it is and why it is in the query. For example, if you are selecting product balances, then name the source stock_balances, not table_two or register. The name should be readable, without spaces, each word capitalized. Well, don't shorten it to one character. This doesn't save memory, it adds confusion. Well, for example, imagine that you made an alias called P with the letter P. And imagine that you then need to find it by search. That is, you press find string, and your letter P appears everywhere thousands or millions of times in all texts. How will you deal with this later? It really doesn't save memory. Name aliases normally. Don't start with an underscore. Such names look system-like and hinder perception. You cannot name a source simply catalog, document. This says nothing. If you are creating a universal mechanism where an arbitrary table is substituted, then it is permissible to use neutral names like table. This is normal when you work, for example, with dynamic reports that are somehow assembled in advance and then a large, large query is assembled from them. or you are making some kind of universal handler where you need to assemble the query text using an algorithm and then pass this query for execution. Then, yes, then you can use some general names like table or attribute_one, attribute_two. But in general, such universal things are written rarely, and they are written by serious developers, because it is difficult. That is, while you are a beginner, try to name everything according to the standard. Here's 758.
Standard 437. Formatting of query texts. First rule: write all keywords in capital letters. Select from, where group by, and so on. It would seem, why am I telling you this? It seems like it's already clear. Well, guys, I've seen so much of other people's code in my life, so just in case, I decided to remind you about this again. Write everything in capital letters. I've seen it written with lowercase letters too. And they start with a capital letter, and then write in lowercase. Write correctly according to the standard, so that it doesn't hurt the eyes, so that everything is clear. What you write not according to the standard, the eye catches on it, and the brain, instead of thinking productively about algorithms, how to do it all, starts to strain and get scared. Why is it written like this? What is this nonsense? Second. Always explicitly specify aliases for fields. Be careful with fields like cash_desk.name. By default, the system will create an alias for currency_name. But it might be more convenient for you to work with an alias like name, so don't forget to write the alias of this field after the word as. This is very important. The query should be formatted as a block with indentation, not on one line. On the screen, you can see an example of how we all usually format it, how the query constructor does it. But just in case, I'll remind you about this so you don't forget. Even if it's a short query, it still looks more structured. Again, the brain will strain less when viewing such queries. If you are working in the query constructor, I'll remind you again, comments are deleted. That is, if you put your comments there, for example, you wrote a query, added a comment between lines or between queries in a query package, yes, you added some comment, then you called the query constructor, and the comment was automatically deleted. The situation is strange, of course. At the same time, in this standard, the 1C company recommends using comments for complex queries. And at the same time, it also writes that these comments will be deleted by the query constructor. There's some strange logic there. Well, they should have made it so that these comments are not deleted. I don't see any problems. So, they decided not to refine it. This is how we work. If you assemble a query programmatically, for example, from several code snippets, dynamically substitute field and table names, then it is recommended to comment all assembly stages. It is recommended to write it so that the query can then be opened in the query constructor without errors. That is, write it so that you right-click, call the query constructor, and it opens in the constructor. Because this helps with debugging, checking. You immediately see that there are no errors in the query. At least, at the first stage. If you are creating a query package or temporarily disabling some part of this construction, then you should not use tricks with commenting inside the text, like putting two slashes. So don't do that. It's better to write the query as if it were complete, and then it is recommended to programmatically delete or replace parts of the query. This will make the code more predictable. This is what this standard advises us.
Standard 729 - code optimization for performance, especially queries and algorithms. Before you start optimizing a query, first ask yourself a simple question: is the query itself okay? Because often the problem is not caused by a crooked index or DBMS, but by a too fat, large query, a too convoluted query, or too many fields are selected from it. It might be doing a lot of unnecessary things. Before thinking about how to connect fields, how to create temporary tables, you first need to think. Maybe I'm just pulling too much information from the DBMS. And this leads to the rule that you should select only the necessary data. If you need two fields, you don't need to select all fields. Second, you don't need to cram everything into one query at any cost. A complex universal query is not always good. Sometimes it's easier to make several simple queries under different conditions or even make a simpler selection. Then refine the result a little in 1C when working in a loop. Maybe it will be faster than if the DBMS starts thinking, composing some giant query plans and trying to execute your complex constructions and trying to calculate some complex fields. Why is this important? Because the optimizer in the DBMS can choose a bad execution plan. It will be very difficult for SQL to choose an optimal query execution plan. Therefore, the recommendations in this standard are that you should not write nested queries just for the sake of beauty. Avoid complex conditions, especially with OR or subqueries. Minimize the number of tables, because even five to seven sources can start to slow down. If you want to check,
How the DBMS will execute your request, look at the VQL Management Studio execution plan, for example. That is, once again, what does this standard tell us? It says that before moving on to more advanced optimization methods, you need to make sure that the query itself is adequate for the task being solved. Minimize the number of queries, meaning remove queries in a loop. Do not try to move all calculations into the query at any cost. That is, perhaps some tasks can be done with a regular loop, by iterating through something and placing it in an array. That is, do not try to move task execution into SQL at any cost. Sending a query to MSSQL involves significant overhead. What else does this standard tell us? That nested queries should not be added solely for improved reporting. I think everything is clear here. And it says that if there are more than seven tables, then the DBMS optimizer in this case has difficulty creating an optimal execution plan. The DBMS optimizer spends a lot of time analyzing the query, which is also very bad. Interesting book. Well, let's continue. From number 657. Accessing virtual tables. The most important rule: all conditions that relate to a virtual table should be passed directly into the table parameters, not written in the WHERE clause. Why is this important? Because if you write a condition in WHERE, 1C might first pull the entire virtual table and then start selecting the necessary records, and this will cause slowdowns, especially on large registers. And time and time again, I still periodically encounter this in the code of other developers, even if they are not beginner programmers. We have a virtual table, for example, balances. They have parameters, and there is a place for us to specify filtering there. And so the DBMS immediately receives a clear command that we need to select virtual tables only by warehouse. But with these parameters, not everything is so simple. You also need to be careful. Only simple conditions are recommended to be put there. Do not put subqueries there. The word "or" or access through a dot, a join. If you put something there, for example, `Document.Reference.IsPosted` or `Document` in a subquery, then the DBMS will most likely choose an inefficient query plan, and the query will start executing slowly. Here, you need to approach each case creatively, so to speak, based on your experience, based on what DBMS we have. Most likely, you will no longer put these conditions into parameters, but, most likely, still into the WHERE clause, or through an inner join, and so on. In general, 1C recommends always using parameters. If it's impossible to use simple parameters, then create a temporary table, fill it with the necessary values, and then use it as a filter. This is more reliable, more readable, and will work faster. One more point. If you have several conditions with subqueries, choose the one that best filters the data and leaves the fewest records. And then put it into the parameters, and apply the rest to the resulting query, i.e., in the WHERE clause. Another point. If you have complex access rights (RLS) that implicitly add subqueries, then the virtual table will start working slowly or may start working slowly. In such cases, use temporary tables or privileged mode, which will disable RLS. You shouldn't be afraid of them; sometimes they are simply necessary. The conclusion is simple: everything that can be put into parameters. Everything complex goes outside. Temporary tables are your friend. 477. Register Self-Sufficiency. It seems short, but I will probably say more than is in this standard. Firstly, the register should be independent of the registrar. All necessary data should be stored within it. What does this mean? That is, if you have, for example, some complex report that gets data from this register, and then you, so to speak, enrich the data from the registrar, for example, pull the status from the customer order. That is, we got data from the register, realized that not all data is there, and decided to access the registrar and get some more data from there. It would be better to add this missing attribute, redesign this register, add this attribute to it, and calculate it immediately in some way, so that everything is stored in the register as much as possible and so that there is no dot access every time the report is generated. Well, here a remark needs to be made that if we have some reports that run once a year and take 10 seconds to generate, then of course we don't need any redesigns. Here it is meant that if your reports are long to build, large data is extracted, or your register is large, or the load on SQL is critical for you, meaning it is overloaded to the fullest, and every millisecond counts, then of course, everything needs to be redesigned immediately, so that everything is in the register. And if you have some kind of accounting database with a small amount of data, then of course, nothing needs to be changed. This standard is about this. Another point here is about distributed databases. They write that accessing registrar fields slows down queries and may not work in a distributed database where the registrar may not exist. Sometimes, according to the settings of a distributed information base, you may have a record that came from a neighboring node, and you cannot enrich the data because the registrar is not there. It remained in another node. This situation can happen, so this must be kept in mind. If you have a distributed database, although personally I consider distributed information bases a great evil, and you should use other methods, of course, try to avoid RDDs as much as possible. I haven't seen anything good from them in my life. The next standard is number 648. Responsible Data Reading. When you read data to change something based on it or transfer it somewhere externally, for example, from a web service, when you return something, meaning an external request came to you via a web service or HTTP service, and you have to return something. For example, perhaps when posting a document, during exchange with an external system, or, say, mass processing of records. Here you need to act responsibly. This standard is about this. What does this mean? It means that you cannot just read data, change it, and save it. This way, you can easily get into a situation where another user has already changed something else in the meantime, and your actions, your data become incorrect. Oops, suddenly. Yes. To prevent this from happening, you must first establish a lock on the data you will be working with. If you plan to change it, it must be an exclusive lock. If you are only reading data, then a shared lock is sufficient. It is strongly recommended that you must handle possible errors. If something goes wrong in the process, you must roll back the transaction and save the error information, for example, in the event log. This approach will protect data from simultaneous conflicts and losses. Well, of course, there is an exception: if you are building a report, displaying a list, or simply searching for data that no one is changing, for example, for some user settings or reference values. Here you can read without locks at all. This also applies to mobile applications, exclusive mode, when you are the only one in the database, or when working with conditionally constant objects or some reference books that never change. But if you change anything after reading the data or transfer it to an external system, you are simply obliged to use locks and transactions. Standard number 783. Correct Use of Transactions for Data Integrity. It slightly overlaps with the previous standard I just described. Let's recall again that a transaction is a way to guarantee that all database operations are either fully executed or not executed at all. This is the essence of a transaction. That is, either all or nothing. It is especially important to use them when you change interrelated data. Many people forget about this. For example, when posting a document, writing to multiple registers, or in some critical business processes. For example, you create an item and a unit of measurement related to it. If something goes wrong when writing the unit of measure, then you need to roll back the entire transaction, meaning do not create the item either. Otherwise, the item will be created without a unit of measure. That is, it will be an erroneous reference book element, which is unclear what it will lead to later, because, as you know, a lot of information is stored in units of measure, some coefficients, some other interrelationships. There are peculiarities with transactions in 1C that need to be considered. Firstly, nested transactions are not supported. This is a feature of the engine. We can open a transaction within a transaction, but in fact, it will not work as a nested transaction. It is essentially a single transaction. Secondly, if an error occurs in the process, and even if we handle it, we will no longer be able to complete the transaction. And an attempt to do so will lead to an exception, so we must roll it back. Therefore, transactions must always be started and ended strictly in pairs. That is, if you open another transaction inside a transaction, you must close both the previous and the nested and the outer ones. Regarding try-catch blocks, the correct approach is also stated here: start the transaction immediately before the try block and then do everything necessary in this block: locks, read, write, and so on. And only at the very end of the block, call commit transaction. And if something goes wrong, in the exception block, you must call rollback transaction, record the error in the log, and, if necessary, re-throw the exception, so to speak, upwards, if there is a higher-level transaction. This approach will help avoid the most difficult-to-debug errors when the platform simply says: "There have already been errors in the transaction." You have probably seen this when such a strange error occurs and it's unclear where to look. According to the rules of this standard, you need to ensure that such an error does not occur, so that the programmer can always quickly diagnose why the error occurred and where to look for it. So, sometimes a transaction can start implicitly, for example, when you call the `Write` method. This is a system event, you don't need to open a transaction at all, it's redundant. And this standard is precisely about this, it does not recommend opening unnecessary transactions, because why? Because 1C will open a transaction automatically when writing. But if you need responsible reading, remember the previous standard, or if you are looking for related objects, then you cannot do without explicitly starting a transaction. This way, you protect yourself from changes that another user might accidentally, or intentionally, make in a parallel session. And when we have multi-user work, someone will definitely log in and change something. So, let's also talk about long transactions. It's important to remember that long transactions are very bad. The standard says this directly: they consume a lot of resources, hold locks, load the server, the database, interfere with other users. And the standard says that if a transaction lasts longer than 20 seconds, then processing needs to be divided into portions, optimize queries, avoid external calls, and do not drag heavy logic into the transaction that can be performed in advance. That is, everything that can be done before the transaction, do it before it. And finally, if you disable totals before writing to a register, then do not forget that this must be done within a transaction, otherwise the system will give an error to other users who try to read the totals at that moment. So, honestly, I find it strange to disable totals for a transaction that lasts 20 seconds. What is that at all? Well, if you know similar examples, write in the comments when you might have encountered this in your practice. I haven't. 465. Event Handling and Object Behavior. Here it is described very briefly, but I will probably say more than is written here. In the object's `OnWrite` event handler, this applies to documents, registers, and constants, actions are usually performed to write related data to other objects or some related processes are launched. Well, it's important to remember that the object itself has already been written to the database at this stage, so why? Therefore, it is forbidden to change the content of the object we are writing in this `OnWrite` event. Some people try to change it all. This is because it's useless and will lead to errors. And another strict rule: all actions in this handler must be performed only after checking the `ExchangeData.Load` flag. This is protection against unnecessary actions when loading data from external systems. Or when you create an object automatically. About this flag. `ExchangeData.Load`, many programmers, even experienced ones, often forget it, and it is very, very important, this flag. Thus, you avoid calculating unnecessary logic in this handler. And if you look at standard configurations, this flag is used everywhere, practically at the very beginning of handlers for writing, after writing, and so on. This is done to facilitate automatic logic. That is, when a user writes something interactively, this flag is naturally not set, and all checks that follow it, all handlers, they trigger. And if we, for example, want to quickly write an object automatically in a scheduled task or from an external system, then we already know that we don't need to do any checks, and we simply set this flag, and we avoid all the unnecessary logic that is written there. A very convenient thing. I recommend using it and understanding how it works more deeply. The next standard, 464, is the `BeforeWrite` event handler. It is called before the object is saved to the database. Here you can perform all necessary checks, correct filling of attributes, whether they are related to external data or not. You need something additional, perhaps to calculate, to fill in. You can also compare current values with those that were before editing. This is often needed in algorithms to understand, aha, what was this attribute before, and what is it now? And based on this, well, perhaps the status has changed, yes, and based on this, write some logic. And here, in this handler, it is recommended to do this. Well, there is a mandatory rule described in this standard. All actions in this handler are also performed after checking the `ExchangeData.Load` flag. It's important to remember this always when you work with these handlers. This is also important so as not to interfere with automatic data loading from other systems. Yes. And beginners constantly forget about this. The next interesting standard is 740 - Secure Password Storage. Quite often, they are stored in plain text. Well, let's see what the standard says. If your subsystem works with external resources, email, web services, FTP, etc., it's better not to store logins and passwords in this database at all. Well, what to do, you might say. Yes, especially in a file-based database. There, any user can copy the file and get access. Great. It's more reliable to ask the user for the password every time and use it without saving. A wonderful recommendation. But what will we do with this next, right? Force the user to enter the password every time. Have any of you tried to do this? Write in the comments. And what did it lead to. Next, what the standard writes. They understand that this will most likely be impossible to do, and they write the following: "If it cannot be avoided." For example, server logic is also required without user participation. Well, you can store the password, but only with awareness of the risks and according to certain rules. So, do not write the password in regular attributes, use a separate object. And in BSP, there is an option to use a secure password storage. There, passwords are stored encrypted, do not get into data exchange, and can only be worked with in privileged mode. On the form, we do not transmit the password in plain text. Instead, we load it on the server and mask it with a unique identifier so that it is not exposed to anyone. And for this, there are even ready-made functions in BSP for working with this secure password storage. Like `WriteDataToSecureStorage`, `ReadDataFromSecureStorage`, `DeleteDataFromSecureStorage`. So, those who still store their passwords in the database or, God forbid, in the code, pay attention. Standard number 499. Exception Handling in Code. It sounds scary. What is this standard about? Let's figure it out. In most cases, you don't need to catch exceptions in 1C. If an error occurs in the code, the platform itself will display it to the user, write it to the event log for the administrator, and, if configured, send it to the error registration service. At the same time, for common situations, standard message templates and recommendations are already provided. However, there are special cases when the technical error text is too unclear to the user. This applies, for example, to problems with external services, email, web interfaces, cryptographic protection. When an error occurs in such an area, you can and should add an explanation to the message so that the person understands what the failure is and what can be done about it. For example, if you cannot send an email, you can display a message to the user like "check the mail server settings." The mail server might give an error, for example, some kind of error 258, right? And if the user sees this, they won't understand anything. Therefore, we must provide them with a human-readable error. The main thing is not to hide the error itself, but to supplement it with a clear comment. And it is important not to wrap all your code in a huge try-catch block. I have seen people start their function with a try-catch block and wrap all their code. Just to not bother with finding errors in the algorithm, as they say. If it fails, it will all fall into the exception, but you shouldn't do that. The standard strictly prohibits this. You need to pinpoint and wrap only the call that might fail. This is written in the syntax assistant, by the way. All those functions that can fail with errors are directly stated there, "an exception will be raised." And it is precisely the call to these functions that we need to wrap in a try-catch block. You don't need to wrap all the code in this try-catch block. This is incorrect. This way, we will not mask errors that are not related to our piece of code at all. In general, I have seen, of course, such huge chunks of code wrapped in try-catch. This, of course, immediately indicates low professionalism of the person doing it. And at the same time, this is very important: it is better not to use outdated functions like `ErrorDescription`. Write in the comments: "Do you still use this `ErrorDescription` function?" Or do you use a more modern approach? Why not use it? Because the standard says that it does not show the call stack, it only shows the current error. And you should also not use a brief error description instead of a detailed message for the user. It is strictly forbidden to completely suppress exceptions, especially without logging. That is, here in the standard, point 32, there is a try block, and nothing in the exception block. This is incorrect. This practice leads to the system administrator, or some responsible user, not seeing what went wrong and being unable to diagnose it. Instead, even if the exception is not shown to the user, it must be logged at least in the event log with an explanation of why it occurred. That is, do not leave the exception section empty, otherwise you will never know about this error. And this will make it difficult for you to find this error later. Because users will come sooner or later and say, "There's an error here." And you won't even know where this error occurred, because your exception section is empty, nothing was shown to anyone, and nothing was recorded anywhere. So, if the Standard Library of Subsystems (BSP) is used, it is better to use the special `ClarifyException` function. It is located in the `CommonModule.General.Server`. You can find it there, and it allows you to carefully supplement the error message while preserving its structure. This makes the text understandable to the user, and enriches the log with useful data for the developer. And, by the way, write, do you use BSP for error handling at all or not? Next, standard number 693. Using Structure Objects. So, what is this standard about? In the 1C language, a structure object is generally convenient for storing sets of named values. When creating a structure, do not pass more than three values to the constructor at once, otherwise the code becomes cumbersome and difficult to read. It's better to create an empty structure altogether, and then add values one by one using the `Insert` method. We see such a cumbersome construction on the screen. It is, of course, very unreadable. But according to the rules, it's clear and precise: which value, which key. In addition, do not create other structures in the structure constructor, especially if they also have parameters. This greatly complicates code reading. It is much clearer when the necessary structures are created separately first, and then these structures are added to another structure. Another mistake is to pass function calls with many parameters directly into the constructor when creating it. This makes the line too long and difficult to analyze. Instead, it's better to save the result of the function call to a variable, and then add it to the structure. If you are working with some temporary structures that you use in many code sections, for example, you created them at the beginning of a function and plan to work with different key values of it. So, the standard says here that you should not add properties to them in different parts of the program and should not check for the existence of these properties using the `Property` method. It's better to immediately create structures with a full set of necessary properties and fill them with default values. This makes the code more reliable and easier to maintain and understand. Because if you create an empty structure at the beginning, and then add properties to it as the algorithm branches, in the end you won't understand which properties have been added and which have not. This will lead to a large number of errors and difficulty in perceiving the code itself. Therefore, create the structure immediately, put all the necessary keys into it, assign them the value `Undefined`, and then work with this structure calmly. So, but if the structure is created based on an external source, for example, if it is the result of an HTTP request or some collected data obtained from a terminal or an HTTP service, then the format becomes non-fixed, because you never know what structure will come from an external source, and then checking for the existence of properties is permissible. That is, you can check if such a key exists using the `Property` method. The same applies to form system parameters or some universal selection parameters. They can also be checked through properties. The standard says this directly, and it is not forbidden. Standard number 781. Peculiarities of Sorting in a Value Table. When you need to apply sorting to columns in a value table that contain references, you need to remember an important feature. With such sorting, the system will request the representation of each reference for each row of the table. That is, the system will pre-process each row, get its representation, and try to sort by it. And if there are many rows, hundreds or thousands, then many database accesses in a loop occur. And this, accordingly, will significantly slow down the execution of this sorting. Therefore, if you need to sort the table by name, it's better to immediately add a separate column to it, into which you will put the representation of the reference. That is, a string name, a string, and then you can sort by it calmly. The main thing here is to ensure that obtaining the representations at the filling stage does not create the same performance problem. That is, sometimes it's simpler and faster, of course, to sort by reference. And if your sorting field is calculated in a cumbersome way, then it's probably easier to sort by reference. However, if you sort by reference, do it using the `ValueComparison` object. By the way, write in the comments, did you know about such an object that exists in standard 1C functions, in the 1C language? There is such an object called `ValueComparison`. By the way, take a look if you didn't know. For this, you create an object, and then call the `Sort` method with the necessary fields and this object. For example, `Date, Reference`. This means that first sorting by date, and then by reference. And this approach provides good performance and avoids unnecessary database accesses for representations. Well, if at least one of these standards resonated with you in some way and was useful, or perhaps made you look at familiar things in a new way, then this episode was a success. Give it a like if it was useful. Share with your team, write in the comments which standards you apply and what you would add to my list of main standards. I wish everyone productive coding and fewer crutches. See you next time. 1C is power. 1C is our fight. [music] We create worlds in the monitor's light. We create worlds in the monitor's light. 1C is power. 1C is power. 1C is power. 1C is our fight.