📜 ⬆️ ⬇️

“The world is a collection of facts, not things”: Wittgenstein and operation-oriented programming

Our programs model the world. Anyone who accepts the PLO’s postulates to heart will quickly face the fact that the modeling process in this method is fundamentally beyond determination. We will discuss in more detail.

Hereinafter I will consider a general book example with employees of the enterprise, we will write on something SI-like. Inherit the Employee class from the Person class is a great idea, especially if you store data exclusively in memory: SQL has some problems with the inheritance of tables, but this is not the point - the OOP with its hierarchism, aggregations, compositions and inheritances offers the ideal way to organize data. Problems with methods.

Behind each method of business logic stands the fact of the world, which this method (more often not alone) models. The facts of programming are operations: we will further call them so. By making a method a member of a class, OOP requires us to bind an operation to an object, which is impossible, because an operation is an interaction of objects (two or more), except in the case of a unary operation, pure reflection. The Payroll (PaySalary) method can be assigned to the classes Employee (Employee), Cash Desk (Cash), Bank Account (Account) - all of them are equivalent in the right of ownership to them. The dilemma about the location of methods accompanies the entire development process: its awkward resolution can be critical and even fatal.
')
In books on programming, honest authors bashfully admit that “objects are not quite objects, as it were,” and OOP is only a way of organizing code, not a modeling mechanism. But the whole point is that “the world is a collection of facts, not things” - hence the fundamental inability to build an adequate model, using the PLO in the form that textbooks require. It is important to understand: in the code it is possible to model the world, but the atoms of the model should be facts, not objects.

Once two years ago in the world of software development, I realized with horror that Aristotle still reigns supreme here: OOP is a direct product of his philosophy. This odious thinker invented phlogiston for chemists, the driving force for physicists - but what can I say! - Attached to each of the major disciplines. The history of European progress is the story of overcoming Aristotle. Science brought more evil than the whole Holy Inquisition. It took our scientists two thousand years to erase the traces of his “Physics”. PLO - the last refuge of his dark shadow. When meeting him here - at the core of the most advanced technologies - I want to take the antique stylus Stimulus (as they used to call the cattle drover in Rome) and drive the evil Greek back into his stone crypt, as everyone else has done.

Ludwig Wittgenstein (his aphorism is in the title) is interesting because, being a Ph.D., I did not read two pages from Aristotle: this is his - Wittgenstein - words. It is not surprising that neopositivism is the only "working" philosophical system, in fact, the only correct philosophy for today: in confirmation I can mention, for example, the neo-positivist Karl Popper, who developed the modern methodology of scientific knowledge.

Functional programming has become the intuitive response of millions of developers to the PLO nebula — its total negation. I myself see no reason to completely abandon the hierarchical principles: they have a lot of convenience. Well-known languages ​​do not offer ready-made tools for building a hierarchy of operations, and now it’s hard for me to imagine what its syntax should look like and sound keywords. It is also possible to transfer the focus from the object to the operation in cash: any known OOP language will cope with this.

To begin with, let's divide business logic into two spaces: data and operations. The data space is nothing special - these are data classes built into the usual hierarchy — living only in main memory (POJO) or with the ability to save state (.NET entities, models YII). Classes of data can have their own methods, as required by the framework: it is important only that these methods are not related to business logic.

Public Class Account { Public string accountBankName; Public string accountMfo; Public string accountNumber; } Public Class Company { Public string companyTitle; Public string companyPhone; Public Account companyAccount; } Public Class Department { Public string departmentTitle; Public Company departmentCompany; } Public Class Person { Public string personName; Public date personBirthDate; } Public Class Employee inherits Person { Public Department employeeDepartment; Public double employeeSalary; Public Account employeeAccount; } 

There is a company (Company) with several divisions (Department) and employees (Employee) inherited from the class of people (Person). The company has an account (Account) from which the salary is transferred to employees. Accordingly, each employee has an account (Account) for salary. Suppose that our program should be able to:

- to hire an employee;
- pay the employee a salary;
- dismiss employee with severance pay;

Hiring and firing an employee can be called personnel (Staff) operations, and the payment of severance pay and salaries - accounting (Accounting) operations.

For each of the operations you will need:

- initialize company data;
- initialize employee data;
- print a document.

To accept / dismiss an employee we will have to:

- initialize the data on the relevant division of the company;

For these two accounting transactions, we also need:

- initialize data on bank accounts of the employee and the company.

We turn to the main thing - the actual space of operations. We implement it as a hierarchy of classes, each of which is something that we call the “Operation Context”. The public methods (APIs) of these classes will be business logic operations, while properties and private methods will help in the formation of abstractions. In accordance with the previously adopted division, in our program the classes StaffOperationContext and AccountingOperationContext, inherited from the base BaseOperationContext, will appear. Putting auxiliary members in the hierarchy of operations will be easier than in the hierarchy of objects.

 Public Class BaseOperationContext { //  BaseOperationContext () { InitCompanyData(); } BaseOperationContext (Employee employee) { InitEmployeeData(Employee employee); InitCompanyData(); } //     private void InitCompanyData(); private void InitEmployeeData(Employee employee); protected void PrintDocument(Document doc); } Public Class AccountingOperationContext inherits BaseOperationContext { //  AccountingOperationContext () { super(); } //     Private InitAccountData(Account account); Private BankTransfer(Account account, Double amount); //   – API  Public void PaySalary (Employee employee) //  / { // …    InitAccountData (employee.employeeAccount); // …    BankTransfer (employee. employeeAccount, salaryAmount); // …    PrintDocument (someSalaryPayDocument); } Public void PayRedundancy (Employee employee) //    { // …    InitAccountData (employee. employeeAccount); // …    BankTransfer (employee. employeeAccount, redundancyAmount); // …    PrintDocument (someRedundancyPayDocument); } } Public Class StaffOperationContext inherits BaseOperationContext { //  StaffOperationContext (Employee employee) { super(employee); } //     Private InitDepartmentData(Department department); //   – API  Public void RecruitEmployee (Person person, Department department) //   { InitDepartmentData(department); Employee employee = person; // …    PrintDocument (someRecruiteDocument); } Public void FireEmployee (Employee employee, Department department) //  { InitDepartmentData(department); // …    //  AccountingOperationContext     AccountingOperationContext accountingOC = new AccountingOperationContext (); accountingOC.PayRedundancy (employee); // ..    PrintDocument (someFireDocument); } } 

With this code, we break the principles of OOP: our FireEmployee method refers to its StaffOperationContext class as “is” and not “contained”: that is, dismissing an employee becomes a particular case of a personnel operation (heir), and not its element (member). Compensation will be gaining common sense. Incorrect statement “dismissal of an employee is a member of the object 'employee'” we replace with a correct “dismissal of an employee is a personnel operation”. The correctness of statements gives hope for the construction of a correct model.

The problem of determination (which method to put) does not seem to be resolved by default, but it is solvable. In my only application developed according to Wittgenstein, I relied on the interface. Having about ten screens on the front end, I divided the logic into ten operations contexts with the base context at the top of the hierarchy.

It is possible to classify operations in different ways, the principle itself is important: we replace object-oriented programming with operation-oriented. I repeat once again: we are talking exclusively about business logic - the world of the program. To use classes of the environment and to generate their successors nothing prevents.

Not having a wide IT erudition, I can assume that such thoughts have already been visited by many philosophers and programmers. Probably, they were even presented in the form of texts - independently and repeatedly. I myself have not met with such research, and it took me two years to come independently to this successful - I think - a combination of object-oriented and functional programming (as it seems outwardly).

In the manner described, I implemented the business logic in my latest project to date. As a result of complete refactoring (the project was inherited by me), the code was reduced by 70% and became incredibly friendly with respect to any - even very significant - edits. The experience was successful: I suggest to try.

Source: https://habr.com/ru/post/312510/


All Articles