قالب وردپرس درنا توس
Home / IOS Development / The worst possible application

The worst possible application



In this article, I deliberately try to write the worst possible application by breaking the primary application design rule: Keep the model and see separately.

The purpose is to try to answer a question clearly: what direct effect does a program design pattern have on code? In an otherwise simple, clean implementation, will a program have obvious errors if I follow the worst possible application design pattern? Will it be dramatically better if I return to a more common pattern?

A subject of contradictions

It is very difficult to make a unique true sentence when talking about application design patterns. The problem is that there is a big gap between the vague descriptions of the high level provided by application design patterns and the specific details of an application implementation. This distance creates a situation where almost everything can be true in the implementation, despite the intentions of the application design pattern.

Out of frustration, with the entire application architecture area, I decided to take another approach: deliberately write an app using the worst possible application design pattern. Perhaps if I made some serious mistakes, they would have dramatic visible effects and I could begin to build a foundation for talking about application design patterns in a clear and unambiguous way.

Searching Disaster

What is the worst possible application design pattern?

I have deliberately designed 5 scenarios that I seriously undermined the typical Cocoa Model-View-Controller pattern and discussed them with 4 different cocoa programmers that I work with presenting each scenario as if I had met it in a real app, to see what mistakes people thought was the worst.

Here are the scenarios I presented:

The app has a 4500 line UIViewController and modification of any method seems to break the behavior of each child's watch.

It was pointed out that this is not a pattern pattern pattern as much as an implementation problem. While it is common ̵

1; at the point of cliché – it can be handled by standard refactor and decomposition steps.

The model contains blocks of CoreGraphics drawing code sent through to Show as a rendering closure.

A few people bought into the premise that this could be a valid approach; Perhaps it does not seem different to pass around SVG data. I still think it has untapped potential as one of the worst ideas imaginable.

The model contains references to each of the app Controllers, and when a value changes, the model directs updates to Controllers. It knows that it depends on the changed value.

This caused some really groans. It may be mostly stupid by the scenarios I presented, but much of the stupidity comes from how easy the solution is (set up correct observers / alerts).

The app has 3 separate Models, and it is View Controller responsibility to keep all of them synced.

If you tell people that you coordinate data from 3 different web services in a single View Controller, it suddenly looks like a reality issue, and people begin to suggest practical solutions.

I decided that the winner was the only application architectural error there:

  • nobody tried to tell me that the problem could be solved with a simple refactoring
  • Nobody bought the idea that you would ever write an app on this way

Drumroll …

There is no own model in the app. Data is stored in any view requiring it and the controls coordinate changes and communication between views.

I think this is one of the least exciting mistakes (after the massive viewer controller), but after decades of having Model View -Controller drummed into our collective thoughts, "Failed to distinguish between model and viewing "are generally agreed to be the worst possible error in application design.

Very good, let's make it happen!

Unprotected

What does "Errors to distinguish model and view" mean?

Unfortunately, like everything else in application design patterns, it's hard to get a concrete response. Outstanding authors have written entire articles about this kind of separation without giving a single clear rule.

I will use the following rules for a divorced model and display :

  1. A Model Interface may
  2. Non-Model Components may invoke mutatory actions on the Model Interface, but the interface must present these as requests to perform an action, not primitive computer operations.
  3. ] After performing an action on the Model Interface, a non-model component may not immediately update or update a non-model status. Updates depending on model data can only occur in response to change alerts from the model.

"Lack of Model and Display Separation" will be defined as violating one or more of these rules.

It can not be immediately clear why I have chosen all these three rules, but in short: it forces the model to be walled off from the rest of the app and forcing the rest of the app to avoid assuming how the model works.

Spoilers: This article will eventually discuss "leakage abstractions". Avoiding leaked abstractions is the real reason why it's so important for non-model components, to avoid predicting how the in-house models work.

It's an app for it

It turns out that I've already shared an example of a program that lacked model and view separation. A few months ago, I shared my "Mines" code from almost 20 years ago. But the use of the 90's Classic MacOS C ++ in the implementation will be in the way of a proper in-depth discussion. So I took a couple of hours and implemented a streamlined version as a Swift iOS app.

I present the final result, without any model spacing, Mines for iOS:

 Figure 1: Mines

Mines for iOS

It seems to be correct: the worst possible application is a Minesweeper clone.

A quick summary of the app

The app's content is the mine field. I have used 100 squares (intended to be presented in a 10 to 10 grid). Each square contains potentially one of the 15 mines, randomly distributed replay, and each square begins to cover, but can be manually flagged or uncovered.

There are no other data required by the app, but I have chosen to cache the number of adjacent mines for each square, and the number of non-mining queries remains uncovered before the game is considered "won". The app contains a "Flag Mode" slider, but the value is considered "Transient Viewing Mode" and is omitted from the model.

If the app had its own model, it would include:

  • The 100 squares, each with is Mine next to and covered values ​​
  • The count nonMineSquaresRemaining
  • Features to generate the routes for a new game (including isMine distribution and next to counts).
  • A feature to handle clicking on a square (which can uncover or flag the square and may need to update nonMineSquaresRemaining

.]

Of course, the app does not have its own model because it would be sensible.

Break all the rules

Let's look at what horrendous things I've done by comparing to the rules I gave for a properly distinguished model.

1. The model must be enclosed and must not refer to the display or application frame [19659056] Each of the SquareView objects (instances of UIButton ) representing tiles in the game contain the following characteristics:

  var    covers :    covers [19659061]      covered 
  was   -MIN .    Boolean    = [19659067]    was    next to :    ] Int8    =    0 

This is not a bufr a representation of state store in another place, this is only representation of the mine field, spread over the 100 different UIButton subclass instances that were used to display the minefield.

It is as big a violation of Rule 1 as possible.

2. The model interface must reveal actions, not primitive data operations

The primary change that occurs during the game updates the state when a square is lost. How is this change happening?

  // In a feature of GameViewController ... 
  squareViews  [  index ].   covers    =  . [19659061] revealed 

In short: when a SquareView has been dropped, it sends its action to GameViewController which maintains a number of SquareView buttons consisting of minefield. GameViewController comes directly into the affected SquareView and directly edits its coverage property.

It is as big a violation of Rule 2 as possible.

3. Updates can only happen in response to change alerts from the model

Here the code is called when you click on a mine:

  if    squareView .   is Mine    {
     squareView    Decker    =      Uncovered 
     NonMineSquaresRemaining    =    - ..   1 
     ] 

] changes either covers the property on a ]

]

]

The GameViewController ] SquareView or nonMineSquaresRemaining value on its own, it may also call setNeedsUpdate on SquareView or refreshSquaresToClear on its own to force an update

It is as big a violation of Rule 3 as possible.

How terrible is the result?

The [1 9459012] GameViewController feels like two completely different classes collapsed - it's obvious to do too many things. and SquareView (a UIButton subclass) contains init to Dictionary () functions to help serialization and deserialization during UIStateRestoration - A task of responsibility that seems hilariously misplaced.

But the app is not a complete mess. The underlying logic remains clear and simple, and none of the work seems powerful or chaotic.

I wanted a horror show, but there is nothing particularly horrible here. Perhaps this experiment does not work.

Correctly Separated Model View-Controller

Perhaps I need a more direct comparison with a more typical Model View-Controller implementation to really highlight what's wrong here.

For this implementation, please refer to the "separated" branch in the depot.

In this version, Square and Game are new types in the new Game.swift file. Game is the interface of the model, the only mutating feature is tapSquare and changes must be observed through Game.changed notice. Square contains game-related data members that were previously on SquareView and Games contain game-related data members previously in GameViewController : [19659055] struct Square : Codable { var covers . covering = covered var -MIN : Boolean = false was adjacent ] Int8 = 0 } [19659151] class Games : Codable { private (

[19659061] private ] : Int }

SquareView still contains the data it needs to draw itself the corresponding Square value from the object Game .

 ] class 

  • SquareView : UIButton { var square : Square { didSet {

      19659060] setNeedsDisplay  ]  } 
    } 

    In addition to moving data members around, the following features from GameViewController were moved to Game ] ] : Int [19659057])
    FUNC newMineField ] mineCount : Int ) -> Array < SquareView [19659166]> {
    FUNC reveal ( squareViews : Array < SquareView > index : Int ) ] -> Int {[19659063] FUNC iterateAdjacent ( squareViews : Array [19659057] < SquareView > index n : : ( ] -> ()) {

and the Button Handling Method on [ViewerController:

  @objc    FUNC    squareTapped  [ ]    Sender : [19659066] Any ) 

has content moved to tapSquare feature of the class Game . This feature is still called from the feature squareTapped on GameViewController . ] [19659000] Since SquareView no longer contains the primary representation of the data, toDictionary () and init (fromDictionary :) contained contained is missing. The new Game example is compatible with Swift Codable so that serialization occurs automatically on the model.

Finally, view updates are handled by making GameViewController Changes to

Game .

  NotificationCenter .   standard .   addObserver  (  sel     selector :    #selector  (  gameChanged  (  _ ]         Game    modified [19659057]    item :    newGame ) 

What shows the comparison ?

There are a handful of minor functionality changes, but the biggest change is that the code has been moved around .

At first glance, it seems to be quite underwhelming – fixing the worst mistake in application design simply involves moving some code around. Surely a small code organization is not really important?

I'm a liar

Before I answer why this is important, I should also be more honest. While "moving a little code around" is superficial effect, there was more work involved. Specifically:

  • I identified the model data
  • I identified all the features that solely focused on model data
  • I created a system of alerts to hand out model data
  • I made sure that the features and alerts provided an easy way to interact with model
  • I implemented an interface around this whole concept.

It's hard to see all this work from a page-by-side analysis even because I had already made most of it the independent version of the app . The idea of ​​separating a model is so rooted in my mind that I had created a de facto model, even when I tried to avoid it.

To mark this instinctive separation, I reorganized the features of GameViewController . Look at GameViewController.swift in the independent version of the Mines app, and you'll see that everything over line 101 in GameViewController is strictly Model Code. It is not separated into another class, but it is not directly mixed with display code either.

Even when I wrote this article, when I gave a "Quick Summary of the App," I used to describe Model even though the app did not technically have a model at that time.

So yes, the Code Comparison largely refers to moving code around, but it's really just making the organization reflect what I've already written – a de facto (if mixed and hidden) abstraction of the game's data and logic.

The model as an abstraction

The distinct model implementation of the app prepares the abstraction offered by the model and strictly enforces it. 19659002] By abstraction, I mean that the interface Game does a lot of hidden work internally, but externally it works simply. It reveals two non-private features – init and tapSquare – which match the two only modes of action available to the user during the game (starting a new game and tapering a square). The output from Game is the mining state communicated through Game.changed notice.

Game abstraction simplifies 100 lines of code to construct and maintain the game down to 2 actions and 1 output. Although there is some work to observe the warning output (about 10 lines in GameViewController ), this abstraction still represents a significant code size and complexity reduction from GameViewController . [19659002] This is the importance of having a model in your application pattern: it makes it possible to reduce the complexity by

  • reducing the steps required for the rest of the program
  • removing the need to ensure correctness from the rest of the program [19659021] removes the need for action sites to know about state observation sites

It is important that the model is a freely chosen abstraction because the controller and view are not. UIViewController and UIView subclasses are collections of functionality, largely dictated by implementation restrictions and requirements for the UIKIT framework. They are not free to model any abstraction. The model is valuable because it is an application-frame-dependent component.

Tough and Bad Abstractions

I have already claimed that it was a de facto model abstraction used before I officially separated the model. However, an abstraction only works to reduce complexity when steps are taken to avoid external components that assume inner work – de facto abstraction offered no real hiding or shielding of its implementation.

When external components assume the inner effect of an abstraction, we call the abstraction a "leaking" abstraction. If there is no no hidden operations of the interface, then it's not really an abstraction at all.

For example, think that when I signed the model, I had not moved all the features of Games :

: Codable {
square : Array [ Square >
var nonMineSquaresRemaining : Int
}

If this was the whole model definition, all work to maintain it would stay on GameViewController just like before. This non-abstracted model interface would not benefit the program.

Application design patterns are potentially useless

This is why I have issues unequivocally discussing application design patterns. A model should give an abstraction, should reduce complexity and improve the location of the code and logic. But if it actually helps, it depends on the implementation. If you choose an useless abstraction for your model, there is potentially no benefit to having it separated.

Application design patterns do not provide implementations, they only suggest some components – which in themselves are completely useless. It is the programmer's responsibility to use good abstractions where possible for these components. After a good application design pattern should lead to good abstractions, but it does not just happen magically.

A useful way to think about application design patterns is like a thought experiment that challenges us to explain our program from different perspectives. An application design pattern asks the question: If you were forced to route updates or actions in the program through an interface between these components, how would you explain the program's state and behavior at that limit? The question encourages an abstraction of the requirements and available alternatives at a particular viewpoint. Whether you consider that the perspective leads to a clean, simple abstraction of the program's behavior, it will determine if the application design pattern is beneficial to your application.

Conclusion

Depending on the model abstraction, any of the following may be true:

  1. Distinguishing the model from the display is mostly about code only.

  2. Separating the model from the view reduces overall implementation complexity by careful abstraction, better separation of concerns, better code positioning and easier data dissemination.

In fact, both of these statements can be descriptions of the same program, as for Mines app.

Writing an application without a separate model is considered the worst application design, not because it immediately leading to disaster, but because it indicates that you have never attempted to isolate clearly what your program does ]. We do not write programs because we love to construct views and inspectors – this is just a means to an end. We write programs to present our model, perform actions on it and see the results.

The worst possible application design is one that does not understand what the app is and conceals its own nature by spreading it.

Looking forward

The Model View Controller pattern typically describes the model that contains all data in the application and view as just a view of the data. However, data that affect display of a view such as scroll mode, navigation status, or non-entered data entry fields – which appear only in the display and not in the model – are apparently violated by the Model View Controller principles.

In the next article, I see what happens when we require this viewing mode to pass through its own model and how this viewing mode can run the entire application.


Source link