There are no best practices, only opinions on how elegant a solution is for a particular problem. Especially true for Ansible, given the flexibility in how it allows you to do things how you want.
Roles are great. Could make a role per app group, and each contains config templates it needs, such as roles/app1/templates/app1.conf.j2 Possibly it has multiple tasks and templates, say if the app starts some process via the service manager, ensures host firewall allows it, and also hooks things up via some service mesh.
app1's defaults/main.yml might contain
instance_name: app1
Which can then get used by tasks/main.yml (some parameters omitted for brevity, perhaps they were moved to module defaults on a block.)
- name: 'Copy config to /opt/conf.d/' template: src: "{{ instance_name }}.conf.j2" dest: "/opt/{{ instance_name }}/conf.d/"
If this all were in one tree alongside your playbook (not required, roles in particular could be stored elsewhere), it might look like the content organization example in the documentation:
playbook.yml roles/ app1/ tasks/ main.yml templates/ app1.conf.j2 app2/ tasks/ main.yml templates/ app2.conf.j2
And the playbook then just needs to list them:
roles: - app1 - app2
Implementation of such roles can be repetitive as every role needs to do the same thing. If roles only differ by a couple variables, make a generic role and call it multiple times with different role parameters.
roles: - role: appgeneric vars: instance_name: app1 - role: appgeneric vars: instance_name: app2
Variables for module parameters is great, the answer to your question is yes. However, variable task names doesn't really work. Task names are global and parsed early, so maybe --extra-vars cli arguments but not say a loop iteration or a host specific variable.
So what to do if you have a generic named role and a generic named task going through 20 app deploys? Errors should be informative, if template cannot load a file it will tell you which file name. Increasing verbosity a bit with ansible-playbook -vv helps understand what's going on, but is messy and verbose. Consider playbook logging software on top of Ansible, like ARA or AWX/Tower. Reports those have showing which execution loaded which variables can provide context.