无逻辑模板(如胡子)的优点是什么?

最近,我碰到了自称是 没有逻辑的模板胡子

然而,没有解释为什么它被设计在无逻辑的方式。换句话说,无逻辑模板的优点是什么?

41667 次浏览

It makes your templates cleaner, and it forces you to keep logic in a place where it can be properly unit-tested.

A logic-less template is a template that contains holes for you to fill, and not how you fill them. The logic is placed elsewhere and mapped directly to the template. This separation of concerns is ideal because then the template can easily be built with different logic, or even with a different programming language.

From the mustache manual:

We call it "logic-less" because there are no if statements, else clauses, or for loops. Instead there are only tags. Some tags are replaced with a value, some nothing, and others a series of values. This document explains the different types of Mustache tags.

In other words, it prevents you from shooting yourself in the foot. In the old JSP days, it was very common to have JSP files sprinkled with Java code, which made refactoring much harder, since you had your code scattered.

If you prevent logic in templates by design (like mustache does), you will be obliged to put the logic elsewhere, so your templates will end up uncluttered.

Another advantage is that you are forced to think in terms of separation of concerns: your controller or logic code will have to do the data massaging before sending data to the UI. If you later switch your template for another (let's say you start using a different templating engine), the transition would be easy because you only had to implement UI details (since there's no logic on the template, remember).

I get the feeling that I am nearly alone in my opinion, but I am firmly in the opposite camp. I don't believe that the possible mixing of business logic in your templates is enough reason not to use the full power of your programming language.

The usual argument for logic-less templates is that if you have full access to your programming language you might mix in logic that has no place being in a template. I find this akin to reasoning that you should use a spoon to slice meat because you could cut yourself if you use a knife. This is very true, and yet you will be far more productive if you use the latter, albeit carefully.

For instance, consider the following template snippet using mustache:

\{\{name}}:
<ul>
\{\{#items}}
<li>\{\{.}}</li>
\{\{/items}}
</ul>

I can understand this, but I find the following (using underscore) to be much more simple and direct:

<%- name %>:
<ul>
<% _.each(items, function(i){ %>
<li><%- i %></li>
<% }); %>
</ul>

That being said, I do understand that logicless templates have advantages (for instance, they can be used with multiple programming languages without changes). I think these other advantages are very important. I just don't think their logic-less nature is one of them.

Even though the question is old and answered, I'd like to add my 2¢ (which may sound like a rant, but it isn't, it's about limitations and when they become unacceptable).

The goal of a template is to render out something, not to perform business logic. Now there's a thin line between not being able to do what you need to do in a template and having "business logic" in them. Even though I was really positive towards Mustache and tried to use it, I ended up not being able to do what I need in pretty simple cases.

The "massaging" of the data (to use the words in the accepted answer) can become a real problem - not even simple paths are supported (something which Handlebars.js addresses). If I have view data and I need to tweak that every time I want to render something because my template engine is too limiting, then this is not helpful in the end. And it defeats part of the platform-independence that mustache claims for itself; I have to duplicate the massaging logic everywhere.

That said, after some frustration and after trying other template engines we ended up creating our own (...yet another...), which uses a syntax inspired by the .NET Razor templates. It is parsed and compiled on the server and generats a simple, self-contained JS function (actually as RequireJS module) which can be invoked to "execute" the template, returning a string as the result. The sample given by brad would look like this when using our engine (which I personally find far superior in readabily compared to both Mustache and Underscore):

@name:
<ul>
@for (items) {
<li>@.</li>
}
</ul>

Another logic-free limitation hit us when calling into partials with Mustache. While partials are supported by Mustache, there is no possibility to customize the data to be passed in first. So instead of being able to create a modular template and reuse small blocks I'll end up doing templates with repeated code in them.

We solved that by implementing a query language inspired by XPath, which we called JPath. Basically, instead of using / for traversing to children we use dots, and not only string, number and boolean literals are supported but also objects and arrays (just like JSON). The language is side-effect-free (which is a must for templating) but allows "massaging" data as needed by creating new literal objects.

Let's say we want to render a "data grid" table with cusomizable headers and links to actions on the rows, and later dynamically add rows using jQuery. The rows therefore need to be in a partial if I don't want to duplicate the code. And that's where the trouble begins if some additional information such as what columns shall be rendered are part of the viewmodel, and just the same for those actions on each row. Here's some actual working code using our template and query engine:

Table template:

<table>
<thead>
<tr>
@for (columns) {
<th>@title</th>
}
@if (actions) {
<th>Actions</th>
}
</tr>
</thead>
<tbody>
@for (rows) {
@partial Row({ row: ., actions: $.actions, columns: $.columns })
}
</tbody>
</table>

Row template:

<tr id="@(row.id)">
@for (var $col in columns) {
<td>@row.*[name()=$col.property]</td>
}
@if (actions) {
<td>
@for (actions) {
<button class="btn @(id)" value="@(id)">@(name)...</button>
}
</td>
}
</tr>

Invocation from JS code:

var html = table({
columns: [
{ title: "Username", property: "username" },
{ title: "E-Mail", property: "email" }
],
actions: [
{ id: "delete", name: "Delete" }
],
rows: GetAjaxRows()
})

It does not have any business logic in it, yet it is reusable and configurable, and it also is side-effect free.

The best argument I've come up with for logicless templates is then you can use the exact same templates on both the client and server. However you don't really need logicless, just one that has it's own "language". I agree with the people who complain that mustache is pointlessly limiting. Thanks, but I'm a big boy and I can keep my templates clean without your help.

Another option is to just find a templating syntax that uses a language that is supported on both client and server, namely javascript on the server either by using node.js or you can use a js interpreter and json via something like therubyracer.

Then you could use something like haml.js which is much cleaner than any of the examples provided so far and works great.

In one sentence: Logic-less means the template engine itself is less complex and therefore has a smaller footprint and there are less ways for it to behave unexpectedly.

Mustache is logic-less?

Isn't this:

\{\{#x}}
foo
\{\{/x}}
\{\{^x}}
bar
\{\{/x}}

Pretty similar to this?

if x
"foo"
else
"bar"
end

And isn't that pretty similar to (read: almost a definition of) presentation logic?

The flip side of the coin is that in a desperate attempt to keep business logic out of the presentation, you end up putting lots of presentation logic in the model. A common example might be that you want to put "odd" and "even" classes on alternating rows in a table, which could be done with a simple modulo operator in the view template. But if your view template doesn't allow you to do that, then in your model data you have to not only store which row is odd or even, but depending on how limited your template engine is, you may even need to pollute your model with the names of actual CSS classes. Views should be separate from Models, full-stop. But Models should also be View agnostic, and that's what many of these "no-logic" template engines make you forget. Logic goes in both places, but you have to be judicious about what the logic actually does to decide correctly where it goes. Is it a presentation concern, or a business/data concern? In an effort to have 100% pristine views, the pollution just lands in another less visible but equally inappropriate place.

There's a growing movement back in the other direction, and hopefully things will center somewhere in the more reasonable middle-ground.

This conversation feels like when the monks of the middle-ages would debate over how many angels can fit on the end of a pin. In other words its starting to feel religious, futile and incorrectly focused.

Mini-rant ensues (feel free to ignore):

If you don't want to continue reading.. My short response to the above topic is: I don't agree with logic-less templates. I think of it as a programming form of extremism. :-) :-)

Now my rant continues in full swing: :-)

I think when you take many ideas to the extreme, the outcome becomes ludicrous. And sometimes (ie this topic) the issue is that we take the "wrong" idea to the extreme.

Removing all logic from the view is "ludicroous" and the wrong idea.

Step back for a moment.

The question we need to ask ourselves is why remove the logic? The concept, obviously is separation of concerns. Keep the view processing as separate from the business logic as possible. Why do this? It allows us to swap out views (for different platforms: mobile,browser,desktop etc) and it allows us to more easily swap out the control-flow, page sequence, validation changes, model changes, security access etc. Also when logic is removed from the views (especially web views), it makes the views much more readable and therefore more maintainable. I get that and agree with that.

However the overriding focus should be on separation of concerns. Not 100% logic-less views. The logic within the views should relate to how to render the "model". As far as I'm concerned, logic in the views is perfectly fine. You can have view-logic that is not business-logic.

Yes, back in the day when we wrote JSPs, PHP or ASP pages with little or no separation of code logic and view logic, the maintenance of these web-apps was an absolute nightmare. Believe me I know, I created and then maintained some of these monstrosities. It was during that maintenance phase that I really understood (viscerally) the error of my and my colleagues ways. :-) :-)

So the edict from on high (the industry pundits) became, thou must structure your web-apps using something like a front-view controller (that dispatched to handlers or actions [pick your web-framework]) and thy views must contain no code. The views were to become dumb templates.

So I agree in general with the above sentiment, not for the specifics of the items of the edict, but rather the motivation behind the edict - which is the desire for separations of concerns between view and business logic.

In one project that I was involved in, we tried to follow the logic-less view idea to the ludicrous extreme. We had a home-grown template engine that would allow us to render model objects in html. It was a simple token based system. It was terrible for one very simple reason. Sometimes in a view we had to decide, should I display this little snippet of HTML .. or not.. The decision is usually based on some value in the model. When you have absolutely no logic in the view, how do you do that? Well you can't . I had some major arguments with our architect about this. The front-end HTML people writing our views were completely hamstrung when they were faced with this and were very stressed because they could not achieve their otherwise simple objectives. So I introduced the concept of a simple IF-statement within our templating engine. I cannot describe to you the relief and the calm that ensued. Problem was solved with a simple IF-Statement concept in our templates! Suddenly our templating engine became good.

So how did we get into this silly stressful situation? We focused on the wrong objective. We followed the rule, you must not have any logic in your views. That was wrong. I think the "rule-of-thumb" should be, minimize that amount of logic in your views. Because if you don't you could inadvertently allow business logic to creep into the view - which violates separation of concerns.

I understand that when you declare that "You must have no logic in the views", it becomes easy to know when you are being a "good" programmer. (If that is your measure of goodness). Now try implementing a web-app of even medium complexity with the above rule. Its not so easily done.

For me, the rule of logic in the views is not so clear cut and frankly that's where I want it to be.

When I see lots of logic in the views, I detect a code-smell and try to eliminate most of the logic from the views - I try to ensure business logic lives elsewhere - I try to separate the concerns. But when I start chatting with people who say we must remove all logic from the view, well, to me, that just smacks of fanaticism as I know you can end up in situations like I described above.

I'm done with my rant. :-)

Cheers,

David

Here are 3 ways of rendering a list, with character counts. All but the first and shortest one are in logic-less templating languages..

CoffeeScript (with Reactive Coffee builder DSL) - 37 chars

"#{name}"
ul items.map (i) ->
li i

Knockout - 100 chars

<span data-bind="value: name"/>
<ul data-bind="foreach: items">
<li data-bind="value: i"/>
</ul>

Handlebars/Mustache - 66 chars

\{\{name}}:
<ul>
\{\{#items}}
<li>\{\{.}}</li>
\{\{/items}}
</ul>

Underscore - 87 chars

<%- name %>:
<ul>
<% _.each(items, function(i){ %>
<li><%- i %></li>
<% }); %>
</ul>

The promise of logic-less templates was, I suppose, that people with broader skillsets would be able to manage logic-less templates without shooting themselves in the foot. However, what you see in the above examples is that when you add a minimal-logic language to string-based markup, the result is more complex, not less. Also, you look like you're doing old-school PHP.

Clearly I don't object to keeping "business logic" (extensive computation) out of templates. But I think that by giving them a pseudo-language for display logic instead of a first class language, the price is paid. Not just more to type, but a heinous mix of context-switching somebody needs to read it.

In conclusion, I fail to see the logic of logic-less templates, so I'd say their advantage is nil to me, but I respect that many in the community see it differently :)

I agree with Brad: the underscore style is easier to understand. But I must admit the syntactic sugar may not please to everybody. If _.each is somewhat confusing, you can use a traditional for loop.

  <% for(var i = 0; i < items.length; i++) { %>
<%= items[i] %>
<% } %>

It is always nice if you can fallback to standard constructs such as for or if. Just use <% if() %> or <% for() %> while Mustache use somewhat neologism for if-then-else (and confusing if you didn't read the documentation):

\{\{#x}}
foo
\{\{/x}}
\{\{^x}}
bar
\{\{/x}}

Template engine is great when you can achieve nested templates easily (underscore style):

<script id="items-tmpl" type="text/template">
<ul>
<% for(var i = 0; i < obj.items.length; i++) { %>
<%= innerTmpl(obj.items[i]) %>
<% } %>
</ul>
</script>


<script id="item-tmpl" type="text/template">
<li>
<%= name %>
</li>
</script>


var tmplFn = function(outerTmpl, innerTmpl) {
return function(obj) {
return outerTmpl({obj: obj, innerTmpl: innerTmpl});
};
};


var tmpl = tmplFn($('#items-tmpl').html(), $('#item-tmpl').html());
var context = { items: [{name:'A',{name:'B'}}] };
tmpl(context);

Basically you pass your inner tmpl as a property of your context. And call it accordingly. Sweet :)

By the way, if the only stuff you are interested in is the template engine, use the standalone template implementation. It is only 900 characters when minified (4 long lines):

https://gist.github.com/marlun78/2701678

The main advantages of using logic-less templates are:

  • Strictly enforces model-view separation, see this paper for details (highly recommended)
  • Very easy to understand and use, because no programming logic (and knowledge!) is needed and the syntax is minimal
  • (particular Mustache) highly portable and language independent, can be used without modification in almost all programming environments