As with any topic of sufficient complexity, the answer depends a lot on the context.
Unfortunately, "when scripting" is a highly ambiguous statement which led to a lengthy discussion on meta.
I'll try to dodge that controversial item by avoiding the term and providing my own context.
Let's first get the most obvious point in the question Ex command vs. function out of the way:
For some Ex commands, there is no corresponding function.
The :new in the question is a good example.
In the absence of a function, use an Ex command.
This works both ways as there are functions that don't mirror an Ex command.
Next, let's have a look on the inherent properties of Ex commands and functions:
| property |
Ex commands |
functions |
| target |
the running Vim instance (:set, :edit) or the current buffer (:substitute, :global) |
the Vim instance or their arguments. Can change the buffer as a side effect |
| calling |
easy, you just need to press : and start typing |
Are relatively awkward to call. In legacy Vimscript, you need to go through one of the commands :call, :let, :if, etc. In Vim9 script, :call became optional, reducing the awkwardness somewhat. |
| arguments |
can have arguments OR operate on a range |
take arguments, which can include lines for a range |
| return value |
don't return anything, their output can be :redirected, however |
can return a value |
| user-definition |
commands can be user-defined, the replacement commands have to be chained with |s, decreasing readability |
Functions can be user-defined, one statement per line |
| autoload |
no such thing |
functions can be autoloaded |
| history |
very old, inherited from ex |
a relatively new feature, support has been and is still growing |
To change the current buffer, an Ex command is usually beneficial because you can directly feed it a range whereas a function would first need another function to pick up a line from the buffer (e.g. getline('.')).
Furthermore, Ex commands are the bread and butter of Vim.
You literally can't quit the editor without the :q command.
An experienced Vim user will know the most commonly used Ex commands.
To have a value returned to do follow-up calculations, a function is the obvious choice.
In everyday editing, you can never touch a function and still get the most out of Vim.
When it comes to user-defined commands, the bar chaining results in poorer readability compared to user-defined functions.
A pattern we see a lot in the user interface of plugins are user-defined commands that call a user-defined (autoloaded) function.
This gives the best of both worlds: an accessible command that leverages the power of functions under the hood.
Let's discuss the two examples in the question:
:execute vs. execute():
As we've seen, we cannot (easily) replace the :new command so let's stick with it.
IMHO, both approaches are flawed.
Romainl once demonstrated (insert link here) that :new expands environment variables, so I'd use the following well-known Ex commands:
:let $filename = "foo.txt"
:new $filename
- How to avoid
:execute: In general, "Ex commands" (not counting those :let, :call, :echo and other "script-like" commands) do not support expression evaluation. This is why we need to do :execute so often. Howevere, there is an exception - filename argument (the VimL parser knows it by special internal flag). So in place of it one can use not only cmdline-special (say, :e #42) but also environment variables (:lcd $HOME/.vim) and backtick-equal" expressions. For example,
" create new buffer in HOME directory
let filename = "foo.txt"
new `=$HOME .. "/" .. filename`
" save session
mksession! `=v:this_session`
To emphasize, this works for things like argadd, new, edit, cd etc. But not for :argdelete or :bwipeout. (If you're lost then open help; whenever it says {file} or {name} it works).
Also, for user commands the only special items are those in angle brackets. However, anything else may still get expanded on the next stage. So the following works:
command! -nargs=* Git !git -C %:p:h:S <args>
Here <args> gets expanded by Git command. And then by :!. Therefore, :Git diff HEAD~1 %:t becomes :!git -C '/path/to/foobar' diff HEAD~1 foobar.txt.
:setlocal vs. setbufvar(): I completely agree with cjs' answer. The :set family of commands is one of the first that new Vim users learn. Not using it borders on code obfuscation.
Let's conclude this question.
When writing Vimscript on your own machine, for your own use, it does not matter.
Take whatever gets the job done, and quickly.
(Most of the time, that will be an Ex command.)
This advice mostly holds true when collaborating with other people, with some additional considerations.
When contributing to an existing project, it's best to adapt to the coding style already used there.
This is general advice for any software project.
When working on a plugin you want to release to other people — it doesn't matter whether this is one tiny .vim file or a huge project — it's usually best to use functions internally (for their scalability and controllable side effects).
Make these functions autoloaded to minimize overhead.
As a user interface, either provide user-defined commands or let the user define their own mappings.